﻿/* ============================================================================
   layout.css — LAYOUT y COMPONENTES de la UI del BackEnd.

   Consume los tokens de tokens.css (cargar ese primero). Cubre:
     1. Shell: sidebar + main (topbar / content / footer)
     2. Sidebar: módulos y opciones, estado actual, rail colapsado con flyout, offcanvas móvil
     3. Topbar: título, breadcrumb, acciones, usuario
     4. Paneles: filtros / resultados / formulario (headers sticky, botonera sticky)
     5. Compatibilidad con el markup de las páginas (card-header-sm, card-body-sm, labels…)
     6. Grilla + paginador (PagerControl: clases apf-pager-*)
     7. Botones, modales, SWAL, toasts, utilidades

   Convenciones:
     - Prefijo `ui-` para todo lo propio. Sin !important (la especificidad la da `.ui-shell`).
     - Los ids que el JS existente necesita NO cambian: #filtersPanel, #btnFilter,
       #btnSearch, #btnShowFields, #btnAyuda, #toast-container, #helpModal.
   ============================================================================ */

/* ---------------------------------------------------------------------------
   1. SHELL
   --------------------------------------------------------------------------- */
html, body.ui-shell { height: 100%; }

body.ui-shell {
    margin: 0;
    background-color: var(--bs-body-bg);
    color: var(--bs-body-color);
    font-size: var(--bs-body-font-size);
    line-height: var(--bs-body-line-height);
    -webkit-font-smoothing: antialiased;
}

body.ui-shell > form { min-height: 100%; }

.ui-app {
    display: flex;
    min-height: 100vh;
    min-height: 100dvh;
}

.ui-main {
    flex: 1 1 auto;
    min-width: 0;            /* deja que las grillas anchas scrolleen dentro, no la página */
    display: flex;
    flex-direction: column;
}

.ui-content {
    flex: 1 1 auto;
    display: flex;
    flex-direction: column;
    gap: var(--ui-content-gap);
    padding: var(--ui-content-pad);
}

/* ---------------------------------------------------------------------------
   2. SIDEBAR
   --------------------------------------------------------------------------- */
/* El <aside> es el "shell" (offcanvas-lg de Bootstrap: en desktop fuerza background
   transparent !important, por eso el fondo va en el .ui-sidebar interno, no en el aside). */
.ui-sidebar-shell {
    --bs-offcanvas-width: var(--ui-sidebar-w);
    --bs-offcanvas-bg:    var(--ui-sidebar-bg);
    --bs-offcanvas-color: var(--ui-sidebar-fg);
    flex: 0 0 auto;
    width: var(--ui-sidebar-w);
    transition: width .18s ease;
}

.ui-sidebar {
    height: 100%;
    background-color: var(--ui-sidebar-bg);
    color: var(--ui-sidebar-fg);
    display: flex;
    flex-direction: column;
    overflow-y: auto;
    overflow-x: hidden;
    scrollbar-width: thin;
    scrollbar-color: rgba(255, 255, 255, .25) transparent;
}

@media (min-width: 992px) {
    .ui-sidebar-shell {
        position: sticky;
        top: 0;
        height: 100vh;
        height: 100dvh;
        z-index: 1030;   /* sobre la topbar (1020) y los headers sticky: el flyout del rail no queda debajo del contenido */
    }
}

/* Marca */
.ui-sidebar-brand {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: .5rem;
    height: var(--ui-topbar-h);
    padding: 0 .75rem 0 .9rem;
    border-bottom: 1px solid var(--ui-sidebar-divider);
    flex: 0 0 auto;
}

.ui-brand-link {
    display: flex;
    align-items: center;
    gap: .6rem;
    min-width: 0;
    color: #fff;
    text-decoration: none;
    font-weight: 600;
    font-size: .95rem;
    letter-spacing: .01em;
}
.ui-brand-link:hover { color: #fff; }

.ui-brand-mark {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 30px;
    height: 30px;
    flex: 0 0 30px;
    border-radius: var(--bs-border-radius);
    background-color: rgba(255, 255, 255, .12);
    font-size: .9rem;
}

.ui-brand-text {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Contexto de sesión bajo la marca (portales: credencial y tipo de socio, razón social del canillita,
   sucursal/comercio logueado). Se oculta en el rail colapsado. */
.ui-sidebar-context {
    padding: .35rem .9rem .1rem;
    font-size: .72rem;
    color: var(--ui-sidebar-fg-muted);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
html[data-ui-sidebar="collapsed"] .ui-sidebar-context { display: none; }

/* Navegación */
.ui-sidebar-nav {
    flex: 1 1 auto;
    padding: .5rem 0;
}

.ui-nav,
.ui-nav-items {
    list-style: none;
    margin: 0;
    padding: 0;
}

.ui-nav-module { position: relative; }

.ui-nav-module-btn {
    display: flex;
    align-items: center;
    gap: .6rem;
    width: 100%;
    padding: .5rem .75rem .5rem .9rem;
    background: transparent;
    border: 0;
    color: var(--ui-sidebar-fg-muted);
    font-size: .72rem;
    font-weight: 600;
    letter-spacing: .06em;
    text-transform: uppercase;
    text-align: left;
    cursor: pointer;
}
.ui-nav-module-btn:hover { color: var(--ui-sidebar-fg); background-color: var(--ui-sidebar-hover-bg); }
.ui-nav-module-btn:focus-visible { outline: 2px solid var(--ui-sidebar-active-bar); outline-offset: -2px; }

.ui-nav-mono {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 26px;
    height: 26px;
    flex: 0 0 26px;
    border-radius: var(--bs-border-radius-sm);
    background-color: rgba(255, 255, 255, .10);
    color: var(--ui-sidebar-fg);
    font-size: .6rem;
    font-weight: 700;
    letter-spacing: .01em;
}

.ui-nav-module-name {
    flex: 1 1 auto;
    min-width: 0;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.ui-nav-caret {
    font-size: .6rem;
    transition: transform .18s ease;
    opacity: .7;
}
.ui-nav-module-btn[aria-expanded="true"] .ui-nav-caret { transform: rotate(90deg); }

/* Módulo de la pantalla actual (clase que pone wucECMSNavigationBarMenuModule) */
.ui-nav-module.menu-module-current > .ui-nav-module-btn { color: #fff; }
.ui-nav-module.menu-module-current > .ui-nav-module-btn .ui-nav-mono {
    background-color: var(--ui-sidebar-active-bar);
    color: var(--ui-sidebar-bg);
}

.ui-nav-items { padding: .1rem 0 .4rem; }

.ui-nav-link {
    display: flex;
    align-items: center;
    gap: .5rem;
    padding: .38rem .75rem .38rem calc(.9rem + 26px + .6rem);
    color: var(--ui-sidebar-fg);
    font-size: .84rem;
    text-decoration: none;
    border-left: 3px solid transparent;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.ui-nav-link:hover,
.ui-nav-link:focus-visible {
    color: #fff;
    background-color: var(--ui-sidebar-hover-bg);
    outline: none;
}
.ui-nav-link.menu-item-current {
    color: #fff;
    font-weight: 600;
    background-color: var(--ui-sidebar-active-bg);
    border-left-color: var(--ui-sidebar-active-bar);
}
.ui-nav-link.disabled {
    opacity: .45;
    pointer-events: none;
}
/* Texto del item, en su propio span (layout.js lo envuelve): truncado con "…" y, al pasar el
   mouse por un item que no entra, circula como marquesina: layout.js agrega una segunda copia
   del texto y la pista se desplaza en loop exactamente una copia (+ separación) por ciclo, así
   el empalme es continuo. Al salir vuelve al truncado. Si el texto entra entero no pasa nada.
   Ídem para el nombre del módulo. */
.ui-nav-text {
    display: block;
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
}
.ui-nav-marquee-track { white-space: nowrap; }
.ui-nav-text.is-marquee,
.ui-nav-module-name.is-marquee { text-overflow: clip; }
.is-marquee > .ui-nav-marquee-track {
    display: inline-block;
    animation: ui-nav-marquee var(--ui-marquee-dur, 6s) linear .25s infinite;
}
.is-marquee .ui-nav-marquee-copy { padding-right: 2.5rem; }   /* separación entre las dos copias */
@keyframes ui-nav-marquee {
    from { transform: translateX(0); }
    to   { transform: translateX(-50%); }   /* la pista son 2 copias: -50% = una copia exacta */
}
@media (prefers-reduced-motion: reduce) {
    .is-marquee > .ui-nav-marquee-track { animation: none; }
}

/* Estrella "novedad" que inyecta el code-behind (ms-auto blink-soft) */
.ui-nav-link .blink-soft {
    margin-left: auto;
    color: #ffd166;
    font-size: .7rem;
}

.ui-nav-divider {
    height: 1px;
    margin: .3rem .75rem .3rem calc(.9rem + 26px + .6rem);
    background-color: var(--ui-sidebar-divider);
}
/* Divider CON título (ECMSAplicaciones.IsDivider = 1 + Nombre): agrupa opciones dentro del módulo */
.ui-nav-group {
    display: block;
    margin: .45rem .75rem .1rem calc(.9rem + 26px + .6rem);
    padding-top: .35rem;
    border-top: 1px solid var(--ui-sidebar-divider);
    color: var(--ui-sidebar-fg-muted);
    font-size: .66rem;
    font-weight: 600;
    letter-spacing: .06em;
    text-transform: uppercase;
}
/* Las filas divider ocultan su link pero el <li> del template queda vacío, y el primer <li> de la
   lista es el título del flyout (oculto): por eso el grupo que ABRE un módulo nunca es :first-child.
   Se lo identifica por la cadena de hermanos y se le saca la línea de arriba; los grupos del medio
   la conservan. El <li> vacío directamente no se muestra. */
.ui-nav-items > li:empty { display: none; }
.ui-nav-items > .ui-nav-flyout-title + li:empty + .ui-nav-group-item > .ui-nav-group,
.ui-nav-items > .ui-nav-flyout-title + .ui-nav-group-item > .ui-nav-group,
.ui-nav-items > li:first-child > .ui-nav-group {
    border-top: 0;
    padding-top: 0;
    margin-top: .2rem;
}

.ui-nav-flyout-title { display: none; }

/* Pie del sidebar: botón contraer/expandir */
.ui-sidebar-foot {
    flex: 0 0 auto;
    border-top: 1px solid var(--ui-sidebar-divider);
    padding: .35rem .5rem;
}

.ui-sidebar-collapse-btn {
    display: flex;
    align-items: center;
    gap: .6rem;
    width: 100%;
    padding: .45rem .4rem .45rem .5rem;
    background: transparent;
    border: 0;
    border-radius: var(--bs-border-radius);
    color: var(--ui-sidebar-fg-muted);
    font-size: .78rem;
    cursor: pointer;
}
.ui-sidebar-collapse-btn:hover { color: #fff; background-color: var(--ui-sidebar-hover-bg); }
.ui-collapse-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 26px;
    flex: 0 0 26px;
    transition: transform .18s ease;
}

/* ---- Rail colapsado (solo desktop): iconos + flyout al pasar el mouse ---- */
@media (min-width: 992px) {
    html[data-ui-sidebar="collapsed"] .ui-sidebar-shell { width: var(--ui-sidebar-w-collapsed); }
    html[data-ui-sidebar="collapsed"] .ui-brand-text,
    html[data-ui-sidebar="collapsed"] .ui-nav-module-name,
    html[data-ui-sidebar="collapsed"] .ui-nav-caret { display: none; }
    html[data-ui-sidebar="collapsed"] .ui-sidebar-brand { padding: 0; justify-content: center; }
    html[data-ui-sidebar="collapsed"] .ui-nav-module-btn { padding: .5rem 0; justify-content: center; }
    html[data-ui-sidebar="collapsed"] .ui-collapse-icon { transform: rotate(180deg); }
    html[data-ui-sidebar="collapsed"] .ui-sidebar-collapse-btn { justify-content: center; padding-left: 0; padding-right: 0; }
    html[data-ui-sidebar="collapsed"] .ui-sidebar { overflow: visible; }   /* el flyout sale del rail */
    html[data-ui-sidebar="collapsed"] .ui-sidebar-nav { overflow: visible; }

    /* Las opciones del módulo se vuelven un flyout a la derecha del rail */
    html[data-ui-sidebar="collapsed"] .ui-nav-items {
        display: none;
        position: absolute;
        left: 100%;
        top: 0;
        min-width: 230px;
        max-width: 320px;
        padding: .35rem 0;
        background-color: var(--ui-sidebar-bg);
        border: 1px solid var(--ui-sidebar-divider);
        border-left: 0;
        border-radius: 0 var(--bs-border-radius) var(--bs-border-radius) 0;
        box-shadow: 8px 8px 24px -10px rgba(0, 0, 0, .45);
        z-index: 1030;
    }
    html[data-ui-sidebar="collapsed"] .ui-nav-module:hover > .ui-nav-items,
    html[data-ui-sidebar="collapsed"] .ui-nav-module:focus-within > .ui-nav-items { display: block; }
    html[data-ui-sidebar="collapsed"] .ui-nav-flyout-title {
        display: block;
        padding: .3rem .9rem .4rem;
        color: var(--ui-sidebar-fg-muted);
        font-size: .68rem;
        font-weight: 600;
        letter-spacing: .06em;
        text-transform: uppercase;
    }
    html[data-ui-sidebar="collapsed"] .ui-nav-link { padding-left: .9rem; }
    html[data-ui-sidebar="collapsed"] .ui-nav-divider { margin-left: .9rem; }
    html[data-ui-sidebar="collapsed"] .ui-nav-group { margin-left: .9rem; }
}

/* ---- Móvil / tablet: Bootstrap offcanvas-lg hace el trabajo; solo el look ---- */
@media (max-width: 991.98px) {
    .ui-sidebar-shell { border-right: 1px solid var(--ui-sidebar-divider); }
}

/* ---------------------------------------------------------------------------
   3. TOPBAR
   --------------------------------------------------------------------------- */
.ui-topbar {
    position: sticky;
    top: 0;
    z-index: 1020;
    display: flex;
    align-items: center;
    gap: .75rem;
    height: var(--ui-topbar-h);
    padding: 0 var(--ui-content-pad);
    background-color: var(--ui-surface);
    border-bottom: 1px solid var(--ui-surface-border);
    box-shadow: var(--ui-surface-shadow);
}

.ui-topbar-menu-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 36px;
    height: 36px;
    border: 1px solid var(--ui-surface-border);
    border-radius: var(--bs-border-radius);
    background: var(--ui-surface);
    color: var(--bs-body-color);
}

.ui-title {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
    justify-content: center;
    line-height: 1.2;
}

.ui-breadcrumb {
    display: flex;
    align-items: center;
    gap: .35rem;
    font-size: .72rem;
    color: var(--bs-secondary-color);
    text-transform: uppercase;
    letter-spacing: .05em;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.ui-breadcrumb i { font-size: .55rem; opacity: .7; }

.ui-title h1 {
    margin: 0;
    font-size: 1.02rem;
    font-weight: 600;
    letter-spacing: .01em;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
/* litContentDescription llega envuelto en <h6> desde el code-behind */
.ui-title h1 h6 { display: none; }

.ui-topbar-actions {
    display: flex;
    align-items: center;
    gap: .5rem;
    flex: 0 0 auto;
}
/* Rol bajo el nombre en el menú de usuario (WebTrackeos: Sucursal / Comercio / Administrador) */
.ui-user-role { display: block; font-size: .68rem; line-height: 1.1; color: var(--bs-secondary-color); text-transform: uppercase; letter-spacing: .04em; }

/* Usuario */
.ui-user-btn {
    display: inline-flex;
    align-items: center;
    gap: .5rem;
    height: 36px;
    padding: 0 .5rem 0 .25rem;
    border: 1px solid transparent;
    border-radius: var(--bs-border-radius-pill);
    background: transparent;
    color: var(--bs-body-color);
    font-size: .8125rem;
    cursor: pointer;
}
.ui-user-btn:hover { background-color: var(--ui-surface-muted); border-color: var(--ui-surface-border); }

.ui-avatar {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 30px;
    height: 30px;
    border-radius: 50%;
    background-color: var(--ui-brand);
    color: var(--ui-brand-contrast);
    font-size: .7rem;
    font-weight: 700;
    letter-spacing: .02em;
}

.ui-user-name { max-width: 180px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ui-user-caret { font-size: .6rem; opacity: .6; }

.ui-user .dropdown-menu { min-width: 220px; font-size: .8125rem; }
.ui-user .dropdown-item { display: flex; align-items: center; gap: .5rem; }

/* ---------------------------------------------------------------------------
   4. PANELES (filtros / resultados / formulario)
   --------------------------------------------------------------------------- */
.ui-panel {
    background-color: var(--ui-surface);
    border: 1px solid var(--ui-surface-border);
    border-radius: var(--bs-border-radius-lg);
    box-shadow: var(--ui-surface-shadow);
}

.ui-panel-head {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: .5rem;
    padding: var(--ui-panel-head-py) var(--ui-panel-px);
    border-bottom: 1px solid var(--ui-surface-border);
    border-radius: var(--bs-border-radius-lg) var(--bs-border-radius-lg) 0 0;
    background-color: var(--ui-surface);
}

.ui-panel-title {
    display: inline-flex;
    align-items: center;
    gap: .5rem;
    margin: 0;
    font-size: var(--ui-section-title-fs);
    font-weight: 600;
    letter-spacing: var(--ui-section-title-ls);
    text-transform: uppercase;
    color: var(--bs-secondary-color);
}

.ui-panel-tools {
    display: inline-flex;
    align-items: center;
    flex-wrap: wrap;
    gap: .35rem;
    margin-left: auto;
}

.ui-panel-body { padding: var(--ui-panel-body-py) var(--ui-panel-px); }

.ui-panel-foot {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: .5rem;
    padding: .6rem var(--ui-panel-px);
    border-top: 1px solid var(--ui-surface-border);
    border-radius: 0 0 var(--bs-border-radius-lg) var(--bs-border-radius-lg);
    background-color: var(--ui-surface);
}

/* Sticky: el header de resultados queda pegado bajo la topbar; la botonera del form, abajo */
.ui-panel-head.ui-sticky {
    position: sticky;
    top: var(--ui-topbar-h);
    z-index: 5;
}
.ui-panel-head.ui-sticky.is-stuck { box-shadow: var(--ui-sticky-shadow); }

.ui-panel-foot.ui-sticky {
    position: sticky;
    bottom: 0;
    z-index: 5;
}
.ui-panel-foot.ui-sticky.is-stuck { box-shadow: 0 -4px 12px -6px rgba(16, 24, 40, .18); }

/* Filtros: menos aire (el panel de filtros es lo primero que se ve, y suele ser 1-2 filas) */
.ui-filters .ui-panel-body { padding-top: .35rem; padding-bottom: .05rem; }
/* Secciones (h6.card-header + .card-body) dentro del panel de filtros: sin padding horizontal propio (lo pone
   el body del panel) y la primera sin línea arriba (el head del panel ya la separa) */
.ui-filters .card-header, .ui-filters .card-body, .ui-filters .card-body-sm { padding-left: 0; padding-right: 0; }
.ui-filters .ui-panel-body > .card-header:first-child,
.ui-filters [class*="col-"] > .card-header:first-child { border-top: 0; padding-top: .15rem; }
.ui-filters .card { border: 0; background: transparent; box-shadow: none; }
.ui-filters .mb-2 { margin-bottom: .4rem; }
.ui-filters .form-text { margin-top: .05rem; }

/* Badge contador de registros */
.ui-count {
    display: inline-flex;
    align-items: center;
    gap: .3rem;
    padding: .1rem .5rem;
    border-radius: var(--bs-border-radius-pill);
    background-color: var(--ui-brand-subtle);
    color: var(--ui-brand-text);
    font-size: .72rem;
    font-weight: 600;
    letter-spacing: 0;
    text-transform: none;
}

/* El panel de filtros abre por default en la UI (ver FiltersPanelPrefs: la preferencia del
   usuario sigue mandando); el botón "Filtros" rota el ícono según estado */
#btnFilter[aria-expanded="true"] .ui-filter-caret { transform: rotate(180deg); }
.ui-filter-caret { font-size: .6rem; transition: transform .18s ease; margin-left: .25rem; }

/* ---------------------------------------------------------------------------
   5. COMPATIBILIDAD CON EL MARKUP DE LAS PÁGINAS
   Las páginas siguen escribiendo <h6 class="card-header card-header-sm">SECCIÓN</h6> +
   <div class="card-body card-body-sm"> dentro de cphFields (y wucECMSAuditoria hace lo
   mismo). Fuera de un .card de Bootstrap esas clases no tienen estilo, así que acá se
   les da el look de "sección" del formulario.
   --------------------------------------------------------------------------- */
.ui-panel .card-header {
    display: flex;
    align-items: center;
    gap: .5rem;
    margin: 0;
    padding: .5rem var(--ui-panel-px) .15rem;
    background: transparent;
    border: 0;
    border-top: 1px solid var(--ui-surface-border);
    font-size: var(--ui-section-title-fs);
    font-weight: 600;
    letter-spacing: var(--ui-section-title-ls);
    text-transform: uppercase;
    color: var(--bs-secondary-color);
}
/* La primera sección del form (sin header propio) no lleva línea arriba */
.ui-panel > .ui-form-sections > .card-body:first-child { padding-top: var(--ui-panel-body-py); }
.ui-panel .card-header .badge { font-size: .66rem; }

.ui-panel .card-body,
.ui-panel .card-body-sm {
    padding: .5rem var(--ui-panel-px) .35rem;
}
.ui-panel .card-footer,
.ui-panel .card-footer-sm { padding: .6rem var(--ui-panel-px); }
/* Cards apiladas DENTRO de un panel (historial de incidencias/pedidos, listas de un tab): un solo aire */
.ui-panel .card + .card { margin-top: var(--ui-content-gap); }

/* Formularios: labels compactos, controles chicos, ayuda discreta */
/* asp:CheckBox con CssClass="form-check-inline" renderiza <span><input><label>: aire entre ambos y
   label del tamaño de los controles. .ui-check-field = un checkbox como campo de filtro/form: se
   baja la altura de un label para que quede alineado con los inputs de las columnas vecinas. */
.ui-shell .form-check-inline { margin-right: 0; }
.ui-shell .form-check-inline input[type="checkbox"] { margin-right: .35rem; vertical-align: -.1em; }
.ui-shell .form-check-inline label { margin: 0; font-size: var(--ui-control-font-size); }
.ui-shell .ui-check-field { padding-top: calc(var(--ui-label-font-size) * 1.5 + .15rem); min-height: calc(1.5em + .5rem + 2px); display: flex; align-items: center; }
.ui-shell .form-label {
    margin-bottom: .15rem;
    font-size: var(--ui-label-font-size);
    font-weight: 500;
    color: var(--bs-secondary-color);
}
.ui-shell .form-label .text-danger { font-weight: 700; }

.ui-shell .form-control,
.ui-shell .form-select,
.ui-shell .input-group-text {
    font-size: var(--ui-control-font-size);
    background-color: var(--ui-surface);
    border-color: var(--ui-surface-border);
    color: var(--bs-body-color);
}
.ui-shell .form-control::placeholder { color: var(--bs-tertiary-color); opacity: 1; }
.ui-shell .form-control-sm,
.ui-shell .form-select-sm { min-height: calc(1.5em + .5rem + 2px); padding: .25rem .5rem; }
.ui-shell .form-control:disabled,
.ui-shell .form-control[readonly],
.ui-shell .form-control.readonly,
.ui-shell .form-select:disabled { background-color: var(--ui-surface-muted); color: var(--bs-secondary-color); }
.ui-shell .form-control.is-invalid,
.ui-shell .form-select.is-invalid { background-color: rgba(220, 53, 69, .06); }
.ui-shell .input-group-text { background-color: var(--ui-surface-muted); }
.ui-shell .input-group .rounded-0 { border-radius: var(--bs-border-radius-sm) 0 0 var(--bs-border-radius-sm); }

.ui-shell small.form-text,
.ui-shell .form-text { font-size: .72rem; color: var(--bs-tertiary-color); }

.ui-shell .form-check-input { margin-top: .15em; border-color: var(--ui-brand-border); }
.ui-shell .mb-2 { margin-bottom: .6rem; }

/* Textareas del form: no dejan redimensionar horizontalmente (rompe la grilla de columnas) */
.ui-shell textarea.form-control { resize: vertical; }

/* ---------------------------------------------------------------------------
   6. GRILLA + PAGINADOR
   --------------------------------------------------------------------------- */
.ui-shell .table-responsive {
    border: 1px solid var(--ui-grid-border);
    border-radius: var(--bs-border-radius);
    scrollbar-width: thin;
    scrollbar-color: rgba(127, 127, 127, .35) transparent;
}
.ui-shell .table-responsive::-webkit-scrollbar { height: 6px; }
.ui-shell .table-responsive::-webkit-scrollbar-thumb { background: rgba(127, 127, 127, .35); border-radius: 3px; }

.ui-shell .table {
    --bs-table-bg:            transparent;
    --bs-table-color:         var(--bs-body-color);
    --bs-table-border-color:  var(--ui-grid-border);
    --bs-table-striped-bg:    var(--ui-grid-striped-bg);
    --bs-table-striped-color: var(--bs-body-color);
    --bs-table-hover-bg:      var(--ui-grid-hover-bg);
    --bs-table-hover-color:   var(--bs-body-color);
    margin-bottom: 0;
    font-size: var(--ui-table-font-size);
}
.ui-shell .table > :not(caption) > * > * {
    padding: var(--ui-table-cell-py) var(--ui-table-cell-px);
    vertical-align: middle;
    border-bottom-width: 1px;
}
.ui-shell .table-bordered > :not(caption) > * > * { border-width: 0 1px 1px 0; }
.ui-shell .table-bordered > :not(caption) > * > *:last-child { border-right-width: 0; }
.ui-shell .table > tbody > tr:last-child > * { border-bottom-width: 0; }

/* Header: ABMHelper.SetStyleToHeaderRow pone class="table-dark" en la fila; el markup
   además pone color:White inline en cada <th>. Se le da el color de marca. */
.ui-shell .table .table-dark,
.ui-shell .table thead {
    --bs-table-bg:           var(--ui-grid-head-bg);
    --bs-table-color:        var(--ui-grid-head-fg);
    --bs-table-border-color: var(--ui-grid-head-border);
    --bs-table-striped-bg:   var(--ui-grid-head-bg);
    --bs-table-hover-bg:     var(--ui-grid-head-bg);
}
.ui-shell .table th {
    font-weight: 600;
    font-size: .74rem;
    letter-spacing: .03em;
    text-transform: uppercase;
    white-space: nowrap;
}
.ui-shell .table th a { color: inherit; text-decoration: none; white-space: nowrap; }
.ui-shell .table th a:hover { text-decoration: underline; }
/* Indicador de orden (port de ParkQR, CSS puro): todo header ORDENABLE (el GridView lo renderiza como <a>)
   lleva SIEMPRE un sufijo — la doble flecha neutra tenue (fa-sort, ::after) cuando no es la columna
   activa, y la flecha ↑/↓ (el <i> que inyecta SetArrowToHeaderCell) cuando lo es — con el MISMO ancho
   fijo, así el header no cambia de ancho al ordenar. La columna activa se resalta con un fondo "foco".
   Los headers no ordenables son texto plano (sin <a>) y no llevan nada. */
.ui-shell .table th a::after,
.ui-shell .table th a > i {
    display: inline-block;
    width: 1.25em;
    margin-left: .3em;
    text-align: center;
    font-size: .95em;
    vertical-align: -.05em;
}
.ui-shell .table th a:not(:has(i))::after {
    content: "\f0dc";                 /* fa-sort (ALWAYS_INCLUDE en Generate-FontAwesomeSubset.py) */
    font-family: "Font Awesome 7 Free";
    font-weight: 900;
    opacity: .45;
}
.ui-shell .table th a:hover::after { opacity: .9; }
.ui-shell .table th a:has(i) {
    padding: .1rem .4rem;
    margin: -.1rem -.4rem;
    border-radius: var(--bs-border-radius-sm);
    background-color: rgba(255, 255, 255, .18);
}
.ui-shell .table th a:has(i):hover { text-decoration: none; background-color: rgba(255, 255, 255, .28); }

/* Botón de fila (Editar/Ver) */
.ui-shell .table .btn-sm { padding: .15rem .5rem; font-size: .74rem; }

/* Recetario de columnas (ver ui-patterns.md §9.5): el ancho va en el markup (HeaderStyle Width),
   estas clases cubren lo que el markup no expresa */
.ui-shell .table .ui-col-actions { text-align: center; white-space: nowrap; }
.ui-shell .table .ui-col-actions .btn { min-width: 0; }
.ui-shell .table td.text-nowrap, .ui-shell .table .ui-col-date { white-space: nowrap; }
.ui-shell .table .ui-col-num, .ui-shell .table .ui-col-money { text-align: right; padding-right: .75rem; font-variant-numeric: tabular-nums; }

/* Estado vacío (wucGridEmptyResults): ícono + título + texto */
.ui-shell .table .alert { margin: 0; border: 0; border-radius: 0; padding: 1.4rem 1rem; background-color: var(--ui-surface-muted); }
.ui-shell .table .alert::before {
    content: "\f002";
    display: block;
    margin-bottom: .4rem;
    font-family: "Font Awesome 7 Free";
    font-weight: 900;
    font-size: 1.4rem;
    color: var(--bs-tertiary-color);
}
.ui-shell .table .alert .fw-bold { font-size: .9rem; }

/* Estado inicial de una consulta (todavía no se buscó: la grilla no se bindeó) */
.ui-results .table-responsive:not(:has(table)) {
    min-height: 120px;
    display: flex;
    align-items: center;
    justify-content: center;
    background-color: var(--ui-surface-muted);
    color: var(--bs-tertiary-color);
    font-size: .85rem;
}
.ui-results .table-responsive:not(:has(table))::before {
    content: "Definí los filtros y presioná Buscar (o Enter) para ver resultados.";
}
.ui-results .table-responsive:not(:has(table)) + .apf-pager { display: none; }

/* Grilla + paginador se leen como una sola pieza */
.ui-shell .table-responsive:has(+ .apf-pager) { border-bottom-left-radius: 0; border-bottom-right-radius: 0; margin-bottom: 0; }

.ui-shell .apf-pager {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: .5rem;
    min-height: 42px;
    padding: .35rem .5rem;
    margin: 0;
    background-color: var(--ui-surface-muted);
    border: 1px solid var(--ui-grid-border);
    border-top: 0;
    border-radius: 0 0 var(--bs-border-radius) var(--bs-border-radius);
    box-sizing: border-box;
}
.ui-shell .apf-pager-left,
.ui-shell .apf-pager-right { display: flex; align-items: center; gap: .5rem; }
.ui-shell .apf-pager-list { display: inline-flex; gap: 2px; list-style: none; margin: 0; padding: 0; }
.ui-shell .apf-pager-item > .apf-pager-link {
    display: block;
    min-width: 30px;
    padding: .2rem .45rem;
    text-align: center;
    font-size: .78rem;
    line-height: 1.4;
    color: var(--bs-body-color);
    background-color: var(--ui-surface);
    border: 1px solid var(--ui-surface-border);
    border-radius: var(--bs-border-radius-sm);
    text-decoration: none;
}
.ui-shell .apf-pager-item.active > .apf-pager-link {
    color: var(--ui-brand-contrast);
    background-color: var(--ui-brand);
    border-color: var(--ui-brand);
    cursor: default;
}
.ui-shell .apf-pager-item.disabled > .apf-pager-link { opacity: .45; pointer-events: none; }
.ui-shell .apf-pager-item:not(.disabled):not(.active) > .apf-pager-link:hover,
.ui-shell .apf-pager-item:not(.disabled):not(.active) > .apf-pager-link:focus-visible {
    color: var(--ui-brand-text);
    background-color: var(--ui-brand-subtle);
    border-color: var(--ui-brand-border);
}
.ui-shell .apf-pager-indicator,
.ui-shell .apf-pager-show-label { font-size: .76rem; color: var(--bs-secondary-color); white-space: nowrap; }
.ui-shell .apf-pager-pagesize { width: auto; font-size: .76rem; padding: .2rem 2rem .2rem .6rem; background-position: right .6rem center; }
.ui-shell .apf-pager-right { gap: .6rem; }

/* ---------------------------------------------------------------------------
   7. BOTONES, MODALES, SWAL, TOASTS, UTILIDADES
   --------------------------------------------------------------------------- */
/* Reemplaza el style="min-width:100px" repetido en cada botón de la botonera */
.ui-btn-action { min-width: 100px; }

.ui-shell .btn { --bs-btn-font-weight: 500; }
.ui-shell .btn-sm { --bs-btn-padding-y: .3rem; --bs-btn-padding-x: .7rem; --bs-btn-font-size: .78rem; }
.ui-shell .btn .fa-solid,
.ui-shell .btn .fa-regular { font-size: .9em; }

/* Botonera del formulario: Confirmar/Actualizar a la derecha, destructivos a la izquierda */
.ui-botonera { display: flex; flex-wrap: wrap; gap: .5rem; width: 100%; }
.ui-botonera .btn-success { margin-left: auto; }
.ui-botonera .btn-success + .btn-success { margin-left: 0; }

/* Modales */
.ui-shell .modal-content { background-color: var(--ui-surface); border-color: var(--ui-surface-border); }
.ui-shell .modal-header.bg-custom { border-bottom: 0; }
.ui-shell .modal-footer.bg-light { background-color: var(--ui-surface-muted); border-top-color: var(--ui-surface-border); }

/* SweetAlert2: mismos ganchos que swal.custom.css, con radios del tema */
.swal2-popup { background: var(--ui-surface); color: var(--bs-body-color); border-radius: var(--bs-border-radius-lg); font-size: .95rem; }
.swal2-modal pre { color: var(--ui-brand-text); font-size: 1.1rem; font-weight: 700; border: 0; text-transform: uppercase; background: transparent; }
.swal2-btn-uniform,
.swal2-custom-confirm,
.swal2-custom-cancel { min-width: 100px; height: 40px; font-size: .9rem; font-weight: 600; border-radius: var(--bs-border-radius); }
.swal-error-popup    { border: 2px solid #dc3545; box-shadow: 0 0 15px rgba(220, 53, 69, .35); }
.swal-success-popup  { border: 2px solid #28a745; box-shadow: 0 0 15px rgba(40, 167, 69, .35); }
.swal-warning-popup  { border: 2px solid #ffc107; box-shadow: 0 0 15px rgba(255, 193, 7, .35); }
.swal-info-popup     { border: 2px solid #17a2b8; box-shadow: 0 0 15px rgba(23, 162, 184, .35); }
.swal-question-popup { border: 2px solid #6610f2; box-shadow: 0 0 15px rgba(102, 16, 242, .35); }

/* Toasts: el contenedor lo posiciona toast-helper.css (fixed, centrado arriba) */
#toast-container .alert { border-radius: var(--bs-border-radius); font-size: .85rem; }

/* Modal de ayuda: el body tiene min-height inline; el texto hereda el tema */
#helpModalContent { color: var(--bs-body-color); }

/* Footer */
.ui-footer {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    margin-top: auto;
    padding: .4rem var(--ui-content-pad);
    border-top: 1px solid var(--ui-surface-border);
    background-color: var(--ui-surface);
    color: var(--bs-tertiary-color);
    font-size: .7rem;
}
.ui-footer-left,
.ui-footer-right { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* Estrella "novedad" del menú (keyframe vivía en estilos-custom-*.css) */
@keyframes blink-soft {
    0%, 100% { opacity: 1; }
    50%      { opacity: .2; }
}
.blink-soft { animation: blink-soft 2.5s ease-in-out infinite; }

/* Botón de tema: muestra el ícono del modo al que se va a cambiar */
[data-ui-theme-toggle] .ui-icon-dark  { display: none; }
[data-ui-theme-toggle] .ui-icon-light { display: inline-block; }
html[data-bs-theme="dark"] [data-ui-theme-toggle] .ui-icon-dark  { display: inline-block; }
html[data-bs-theme="dark"] [data-ui-theme-toggle] .ui-icon-light { display: none; }

/* ---------------------------------------------------------------------------
   8. TABS — UNA sola definición para toda la UI (formularios ABM, tableros, utilidades).
   Markup = convención de ParkQR. En un ABM va dentro de .pqr-tabs-container (pqr-tabs.js
   persiste el tab activo en hfActiveTab y UITabs navega a los errores); en un tablero alcanza
   con <ul class="nav nav-tabs"> + <div class="tab-content">: el look es el mismo.
   Clases de estado (las pone pqr-tabs.js / UITabs): .pqr-tab-error, .disabled, .d-none (tab vacío).
   Sin colores fijos: pqr-tabs.css de PQ y los estilos por página (.dash-tabs, .nav-tabs-integraciones) NO se usan.
   --------------------------------------------------------------------------- */
.ui-shell .nav-tabs {
    --bs-nav-tabs-border-width: 0;
    --bs-nav-link-padding-y: .55rem;
    --bs-nav-link-padding-x: .8rem;   /* 9 tabs (Beneficios) tienen que entrar a 1366px sin scroll */
    --bs-nav-link-font-size: .8rem;
    --bs-nav-link-font-weight: 500;
    --bs-nav-link-color: var(--bs-secondary-color);
    --bs-nav-link-hover-color: var(--ui-brand-text);
    --bs-nav-tabs-link-active-color: var(--ui-brand-text);
    --bs-nav-tabs-link-active-bg: transparent;
    --bs-nav-tabs-link-active-border-color: transparent;
    gap: .15rem;
    margin: 0;
    padding: 0 var(--ui-panel-px);
    border: 1px solid var(--ui-surface-border);
    border-radius: var(--bs-border-radius-lg) var(--bs-border-radius-lg) 0 0;
    background-color: var(--ui-surface-muted);
    overflow-x: auto;
    overflow-y: hidden;   /* overflow-x:auto arrastra overflow-y a auto: sin esto aparecía un mini scroll vertical */
    flex-wrap: nowrap;
    scrollbar-width: thin;
}
.ui-shell .nav-tabs .nav-item { flex: 0 0 auto; }
.ui-shell .nav-tabs .nav-link {
    display: inline-flex;
    align-items: center;
    gap: .45rem;
    border: 0;
    border-bottom: 2px solid transparent;
    border-radius: 0;
    margin-bottom: 0;
    white-space: nowrap;
    background: transparent;
}
.ui-shell .nav-tabs .nav-link i { font-size: .85em; opacity: .8; }
.ui-shell .nav-tabs .nav-link:hover { border-bottom-color: var(--ui-brand-border); }
.ui-shell .nav-tabs .nav-link.active { font-weight: 600; border-bottom-color: var(--ui-brand); }
.ui-shell .nav-tabs .nav-link.active i { opacity: 1; }
.ui-shell .nav-tabs .nav-link.disabled,
.ui-shell .nav-tabs .nav-link:disabled { color: var(--bs-tertiary-color); opacity: .6; cursor: not-allowed; pointer-events: none; }
/* Tab con campos inválidos: punto rojo + texto en rojo (lo marca UITabs / pqrTabs.setTabErrors) */
.ui-shell .nav-tabs .nav-link.pqr-tab-error { color: var(--bs-danger); }
.ui-shell .nav-tabs .nav-link.pqr-tab-error::after {
    content: "";
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background-color: var(--bs-danger);
    box-shadow: 0 0 0 2px var(--ui-surface);
}
.ui-shell .nav-tabs .nav-link.active.pqr-tab-error { border-bottom-color: var(--bs-danger); }

/* Tab content: en un tablero, el contenido de los tabs sigue al nav como un panel */
.ui-shell .tab-content { padding-top: .25rem; }
/* `~` y no `+`: entre el <ul> y el .tab-content va el HiddenField hfActiveTab (pqr-tabs.js) */
.ui-content-free .nav-tabs ~ .tab-content {
    padding: var(--ui-panel-body-py) var(--ui-panel-px);
    margin-top: calc(-1 * var(--ui-content-gap));   /* pegado al nav (el flex gap lo separaría) */
    border: 1px solid var(--ui-surface-border);
    border-top: 0;
    border-radius: 0 0 var(--bs-border-radius-lg) var(--bs-border-radius-lg);
    background-color: var(--ui-surface);
}

/* Dentro de un panel de ABM el nav no lleva bordes propios: los da el panel */
.ui-panel .pqr-tabs-container { padding: 0; }
.ui-panel .pqr-tabs-container > .nav-tabs { border-width: 0 0 1px; border-radius: 0; }
.ui-panel .pqr-tabs-container > .tab-content > .tab-pane { padding-top: .25rem; }
.ui-panel .tab-pane > .card-header:first-child { border-top: 0; }

/* Contexto de la entidad (pqrTabs.setContext), a la derecha de los tabs */
/* Contexto "#Id · nombre": PQ lo pone primero, en la UI va al final. Es ELÁSTICO: ocupa el espacio que
   sobra (hasta 40%) y se recorta con "…" — nunca empuja a los tabs ni fuerza el scroll horizontal
   (la barra es nowrap + overflow-x:auto; un nombre largo hacía aparecer el scroll al tipear). */
.ui-shell .nav-tabs .pqr-tab-context-item { order: 99; margin-left: auto; align-self: center; padding: 0 .25rem; flex: 1 1 auto; min-width: 0; max-width: 40%; justify-content: flex-end; }
.ui-shell .pqr-tab-context { font-size: .78rem; color: var(--bs-secondary-color); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%; }

/* Ocultar botones de acción salvo "volver" en tabs operativos (data-hide-buttons, contrato PQ) */
.pqr-botonera.pqr-hide-action-buttons > *:not(.pqr-btn-back) { display: none; }

@media (prefers-reduced-motion: reduce) {
    .ui-shell .tab-pane.fade { transition: none; }
}

/* ---------------------------------------------------------------------------
   9. GRILLA CON SCROLL INTERNO + HEADER STICKY — <html data-ui-grid="scroll"> (default;
   toggle en el menú de usuario → "page" = la grilla crece y scrollea la página).
   El GridView de WebForms no emite <thead>: el header es la <tr class="table-dark"> que pone
   ABMHelper.SetStyleToHeaderRow, por eso el sticky va sobre sus <th>.
   --------------------------------------------------------------------------- */
html[data-ui-grid="scroll"] .ui-results .table-responsive:has(table) {
    max-height: var(--ui-grid-max-h);
    overflow: auto;
}
html[data-ui-grid="scroll"] .ui-shell .table .table-dark > th,
html[data-ui-grid="scroll"] .ui-shell .table thead th {
    position: sticky;
    top: 0;
    z-index: 3;
    box-shadow: inset 0 -1px 0 var(--ui-grid-head-border);
}
/* En modo página el header de la grilla NO es sticky: .table-responsive es un contenedor de
   scroll (overflow-x: auto para las grillas anchas) y un th sticky se pegaría ADENTRO de la
   tabla, desplazado (bug 2026-09-18). Lo que queda pegado en ese modo es el header del panel
   de Resultados (.ui-panel-head.ui-sticky). */

/* ---------------------------------------------------------------------------
   10. REFRESH PARCIAL DE GRILLA — overlay "Actualizando…" (pqr-partial-grid.js)
   --------------------------------------------------------------------------- */
.pqr-grid-overlay-host { position: relative; }
.pqr-grid-overlay {
    position: absolute;
    inset: 0;
    z-index: 20;
    display: flex;
    align-items: flex-start;
    justify-content: center;
    background-color: rgba(var(--bs-body-bg-rgb), .55);
    backdrop-filter: blur(1px);
    cursor: progress;
}
.pqr-grid-overlay-content {
    display: flex;
    align-items: center;
    gap: .5rem;
    margin-top: 90px;
    padding: .5rem 1rem;
    border-radius: var(--bs-border-radius);
    background-color: var(--ui-surface);
    border: 1px solid var(--ui-surface-border);
    box-shadow: var(--ui-sticky-shadow);
    font-size: .85rem;
    color: var(--bs-body-color);
}
.pqr-grid-overlay-sm { align-items: center; }
.pqr-grid-overlay-sm .pqr-grid-overlay-content { margin-top: 0; }

/* ---------------------------------------------------------------------------
   11. BUSCADOR DEL MENÚ (Ctrl+K) — filtra los módulos/opciones del sidebar en vivo
   --------------------------------------------------------------------------- */
.ui-nav-search-wrap { padding: .5rem .6rem .25rem; position: relative; }
.ui-nav-search {
    width: 100%;
    padding: .38rem .6rem .38rem 1.9rem;
    border: 1px solid var(--ui-sidebar-divider);
    border-radius: var(--bs-border-radius);
    background-color: rgba(255, 255, 255, .07);
    color: #fff;
    font-size: .8rem;
    outline: none;
}
.ui-nav-search::placeholder { color: var(--ui-sidebar-fg-muted); }
.ui-nav-search:focus { border-color: var(--ui-sidebar-active-bar); background-color: rgba(255, 255, 255, .1); }
.ui-nav-search-wrap > i { position: absolute; left: 1.2rem; top: 50%; transform: translateY(-40%); font-size: .75rem; color: var(--ui-sidebar-fg-muted); pointer-events: none; }
.ui-nav-search-kbd { position: absolute; right: 1.1rem; top: 50%; transform: translateY(-40%); font-size: .6rem; color: var(--ui-sidebar-fg-muted); border: 1px solid var(--ui-sidebar-divider); border-radius: 3px; padding: 0 .3rem; pointer-events: none; }
.ui-nav-search-wrap.is-active .ui-nav-search-kbd { display: none; }
.ui-nav-module.ui-nav-hidden, .ui-nav-items li.ui-nav-hidden { display: none; }
.ui-nav-link.ui-nav-match { background-color: var(--ui-sidebar-hover-bg); }
.ui-nav-empty { padding: .75rem .9rem; color: var(--ui-sidebar-fg-muted); font-size: .8rem; display: none; }
.ui-sidebar-nav.is-filtering .ui-nav-empty.show { display: block; }
html[data-ui-sidebar="collapsed"] .ui-nav-search-wrap { display: none; }

/* ---------------------------------------------------------------------------
   12. BANNER SIN CONEXIÓN (offline-banner.js) y CONTEXTO DE TABS
   --------------------------------------------------------------------------- */
.ui-offline-banner {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    z-index: 2000;
    padding: .45rem 1rem;
    text-align: center;
    font-size: .85rem;
    font-weight: 500;
    background-color: var(--bs-warning-bg-subtle);
    color: var(--bs-warning-text-emphasis);
    border-bottom: 2px solid var(--bs-warning);
}

/* ---------------------------------------------------------------------------
   13. CONTENIDO LIBRE (LayoutLogued / LayoutLoguedDashBoard): las páginas traen sus
   propias cards de Bootstrap; se les da superficie y borde del tema (dark incluido).
   --------------------------------------------------------------------------- */
.ui-content-free .card {
    --bs-card-bg: var(--ui-surface);
    --bs-card-border-color: var(--ui-surface-border);
    --bs-card-cap-bg: var(--ui-surface-muted);
    --bs-card-cap-color: var(--bs-body-color);
}
.ui-content-free .card {
    box-shadow: var(--ui-surface-shadow);
    border-radius: var(--bs-border-radius-lg);
}
/* .card-header-sm / .card-body-sm / .card-footer-sm: mismas medidas que los paneles de ABM/RPT
   (estilos.css lo hacía con !important; acá con tokens) */
.ui-content-free .card > .card-header,
.ui-content-free .card > .card-header-sm {
    padding: var(--ui-panel-head-py) var(--ui-panel-px);
    font-size: var(--ui-section-title-fs);
    font-weight: 600;
    letter-spacing: var(--ui-section-title-ls);
    text-transform: uppercase;
    color: var(--bs-secondary-color);
    background-color: var(--ui-surface);
    border-bottom: 1px solid var(--ui-surface-border);
}
.ui-content-free .card > .card-body,
.ui-content-free .card > .card-body-sm { padding: var(--ui-panel-body-py) var(--ui-panel-px); }
.ui-content-free .card > .card-footer,
.ui-content-free .card > .card-footer-sm { padding: .45rem var(--ui-panel-px); background-color: var(--ui-surface-muted); border-top: 1px solid var(--ui-surface-border); }
/* Widgets KPI de color de marca (Dashboard-Responsive) */
.ui-content-free .card.bg-custom { border-color: transparent; }
.ui-content-free .card.bg-custom .p-2 { padding: .6rem .8rem; }
.ui-content-free .card.bg-custom h2 { font-size: 1.6rem; font-weight: 700; margin-bottom: .2rem; }
.ui-content-free .card.bg-custom p { margin-bottom: 0; font-size: .82rem; }
/* Imagen de un QR dentro de un modal / card (WebTrackeos Trackear, ABMSucursales): responsive, con
   borde redondeado y sombra, mismo look que ViewQR de ParkQR. */
.ui-shell .qr-image { width: 100%; max-width: 320px; height: auto; border-radius: var(--bs-border-radius); box-shadow: 0 4px 12px rgba(0, 0, 0, .15); }
@media (max-width: 576px) { .ui-shell .qr-image { max-width: 260px; } }
/* Utilidades de layout que vivían en estilos.css (equal-height / vertilcal-scroll) */
.ui-content-free .row.equal-height { display: flex; flex-wrap: wrap; }
.ui-content-free .row.equal-height > [class*='col-'] { display: flex; flex-direction: column; }
.ui-content-free .row.equal-height .card { flex: 1; }
.ui-content-free .vertilcal-scroll { flex: 1 1 0; display: flex; flex-direction: column; overflow-y: auto; scrollbar-width: thin; }
/* Cuerpo con scroll de un widget de lista del tablero (Novedades, Vencimientos del mes): alto acotado por
   token, sin min-height/max-height inline en el markup. */
.ui-shell .ui-widget-scroll { max-height: var(--ui-widget-scroll-max); overflow-y: auto; scrollbar-width: thin; }
/* Cuerpo de un widget de lista del tablero: se estira entre header y footer (footer pegado abajo) PERO
   acotado por el token de alto (--ui-widget-scroll-max) para que un widget con miles de registros
   (Novedades) no infle la fila entera; si el contenido excede, scrollea. flex fija el piso (llena el
   hueco aunque haya pocos ítems), max-height fija el techo, min-height:0 habilita el scroll del flex item. */
.ui-shell .ui-widget-fill { flex: 1 1 auto; min-height: 0; max-height: var(--ui-widget-scroll-max); overflow-y: auto; scrollbar-width: thin; }
/* Un asp:UpdatePanel envuelve su contenido en un <div> sin clase que corta la cadena de alto
   (col h-100 → [div del UpdatePanel] → card h-100 quedaba a la altura del contenido). Este hook, en el
   wrapper del UpdatePanel, hace que ese div ocupe el 100% y la card recupere el alto de la fila. */
.ui-shell .ui-upnl-fill, .ui-shell .ui-upnl-fill > div { height: 100%; }
/* UN solo aire entre widgets (--ui-content-gap = G), UNA sola fuente (este bloque), en cualquier nivel.
   Cómo se arma, porque Bootstrap mete su propia matemática:
     - una .row lleva margin-top: -G (su gutter-y) y CADA columna margin-top: +G → la primera línea de
       columnas arranca a ras (−G + G = 0) y las líneas siguientes quedan a G.
     - hijos DIRECTOS de main.ui-content: los separa el flex gap (G). Una fila que sigue conserva su −G y
       sus columnas ponen +G → G neto. Ahí NO va ningún margen extra.
     - anidados (dentro de un tab-pane, una card, un UpdatePanel, una columna) no hay flex gap: una fila
       que sigue a otra fila o a una card lleva margin-top 0 (sus columnas ya ponen +G → G neto); una card
       que sigue a una fila o a otra card lleva margin-top G.
   Los mt/mb-N que las páginas pongan en filas, columnas y cards NO cuentan (Clean-Markup.py los saca;
   las guardas de abajo cubren lo que se escape). Los mb-* DENTRO del contenido de una card sí valen. */
.ui-content-free .row { --bs-gutter-x: var(--ui-content-gap); --bs-gutter-y: var(--ui-content-gap); }
.ui-content-free .row > * { margin-bottom: 0 !important; }
.ui-content-free .row.mb-1, .ui-content-free .row.mb-2, .ui-content-free .row.mb-3,
.ui-content-free .card.mb-1, .ui-content-free .card.mb-2, .ui-content-free .card.mb-3 { margin-bottom: 0 !important; }
/* anidados */
.ui-content-free .row + .row,  .ui-content-free .card + .row  { margin-top: 0; }
.ui-content-free .card + .card, .ui-content-free .row + .card { margin-top: var(--ui-content-gap); }
/* hijos directos de main.ui-content: el flex gap ya separa; la fila vuelve a su −G de Bootstrap */
.ui-content-free > .row + .row,  .ui-content-free > .card + .row  { margin-top: calc(-1 * var(--bs-gutter-y)); }
.ui-content-free > .card + .card, .ui-content-free > .row + .card { margin-top: 0; }
/* guardas: un mt-N que se escape no rompe el aire (las utilidades de Bootstrap llevan !important) */
.ui-content-free .row + .row.mt-1, .ui-content-free .row + .row.mt-2, .ui-content-free .row + .row.mt-3 { margin-top: 0 !important; }
.ui-content-free > .row + .row.mt-1, .ui-content-free > .row + .row.mt-2, .ui-content-free > .row + .row.mt-3 { margin-top: calc(-1 * var(--bs-gutter-y)) !important; }
/* un alert (aviso de un tab) antes de un widget: mismo aire */
.ui-content-free .alert + .row, .ui-content-free .alert + .card { margin-top: 0; }
.ui-content-free .tab-pane > .alert { margin-bottom: var(--ui-content-gap); }
/* Separadores dentro de una card: un solo margen (las páginas traían hr con estilos inline distintos) */
.ui-content-free .card hr { margin: .4rem 0; opacity: .15; }
.ui-content-free .card .list-group-flush .list-group-item { padding: .35rem var(--ui-panel-px); }
/* Filtros de los tableros: los 5 con fechas son un panel ui-filters como los de ABM/RPT (head con
   período + presets + Aplicar, body con los campos). wucDashboardPeriodo (Programa) conserva su
   .dash-filter propio: acá se le da el mismo panel. */
.ui-content-free .dash-filter:not(.ui-panel) {
    padding: .45rem var(--ui-panel-px);
    border: 1px solid var(--ui-surface-border);
    border-radius: var(--bs-border-radius-lg);
    background-color: var(--ui-surface);
    box-shadow: var(--ui-surface-shadow);
}
.ui-content-free .dash-filter .fs-5 { font-size: .95rem !important; }   /* el período del wucDashboardPeriodo: Bootstrap lo fija con !important */
/* Los campos de los filtros de un tablero son EXACTAMENTE los de cphBlockFilters de un ABM:
   row > col-xl-3 col-lg-3 col-md-6 col-sm-6 > div.mb-2 > label.form-label + control (o el
   wucDateFlatPickr, que ya trae esa estructura). Por eso la fila de filtros recupera el gutter
   estándar de Bootstrap (el gutter reducido de .ui-content-free es para los widgets, no para
   los campos) y no hay reglas propias de label/select: son las de §6. */
.ui-content-free .ui-filters .row { --bs-gutter-x: 1.5rem; --bs-gutter-y: 0; }
.ui-content-free .ui-filters .btn-group .btn { font-size: .78rem; }
/* Tarjetas métricas (wucMetricCard) */
.ui-content-free .metric-card .card-body { padding: .7rem .85rem; }
.ui-content-free .metric-lbl, .ui-content-free .metric-deltatxt { color: var(--bs-secondary-color); }
/* Presentación base de las metric cards. Antes vivía en dashboards.css (que solo carga la master de
   tableros), pero las metric cards se usan también FUERA de tableros (widgets de Gaceteros/portales),
   así que van en el layout —que se carga en todas las páginas y lo copia Sync-Assets-to-Portals.cmd—. */
.ui-shell .metric-card .card-body { padding: .9rem 1rem; }
.ui-shell .metric-ico { width: 44px; height: 44px; border-radius: 50%; display: grid; place-items: center; font-size: 20px; flex: 0 0 auto; background: color-mix(in srgb, currentColor 13%, transparent); }
.ui-shell .metric-val { font-size: 1.7rem; font-weight: 750; line-height: 1.05; letter-spacing: -.02em; font-variant-numeric: tabular-nums; }
.ui-shell .metric-foot { font-size: .75rem; }
.ui-shell .metric-delta { font-weight: 700; }
.ui-content-free .apex-grid .card .card-header { border-bottom: 0; }
/* ApexCharts pinta textos con fill/color propios (inline): en dark mode quedarían negros. Es el
   único lugar de la UI con !important, y solo para el color de texto de los gráficos. */
.ui-shell .apexcharts-text tspan { fill: var(--bs-body-color); }
.ui-shell .apexcharts-legend-text { color: var(--bs-body-color) !important; }
.ui-shell .apexcharts-gridline { stroke: var(--ui-grid-border); }
.ui-shell .apexcharts-tooltip { background: var(--ui-surface) !important; color: var(--bs-body-color) !important; border-color: var(--ui-surface-border) !important; }

/* ---------------------------------------------------------------------------
   14. LAYOUT PÚBLICO (LayoutPublic: login, recuperación de clave, páginas de error)
   --------------------------------------------------------------------------- */
.ui-public { min-height: 100vh; min-height: 100dvh; display: flex; flex-direction: column; }
/* Topbar pública = cabecera del sidebar (marca, fondo oscuro) + topbar logueada (título, usuario) en una sola banda */
.ui-public-topbar {
    position: sticky;
    top: 0;
    z-index: 1020;
    display: flex;
    align-items: stretch;
    gap: .75rem;
    height: var(--ui-topbar-h);
    background-color: var(--ui-surface);
    border-bottom: 1px solid var(--ui-surface-border);
    box-shadow: var(--ui-surface-shadow);
}
.ui-public-topbar .ui-brand-link { flex: 0 0 auto; padding: 0 1rem 0 .9rem; background-color: var(--ui-sidebar-bg); color: #fff; }
.ui-public-topbar .ui-title { padding-left: .25rem; }
.ui-public-topbar .ui-topbar-actions { margin-left: auto; padding-right: var(--ui-content-pad); }
.ui-public-content { flex: 1 1 auto; width: 100%; max-width: 1140px; margin: 0 auto; padding: 1.5rem var(--ui-content-pad); }
.ui-public-content .card { --bs-card-bg: var(--ui-surface); --bs-card-border-color: var(--ui-surface-border); box-shadow: var(--ui-surface-shadow); }
.ui-public-content .card-header { background-color: var(--ui-surface-muted); font-weight: 600; }

/* ---------------------------------------------------------------------------
   15. POPOVERS (Bootstrap; los inicializa custom.js) — botón ⓘ discreto + popover con tokens
   --------------------------------------------------------------------------- */
.ui-info-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 20px;
    height: 20px;
    margin-left: .1rem;
    padding: 0;
    border: 0;
    border-radius: 50%;
    background: transparent;
    color: var(--bs-tertiary-color);
    font-size: .85rem;
    text-transform: none;
    letter-spacing: 0;
    cursor: help;
}
.ui-info-btn:hover, .ui-info-btn:focus { color: var(--ui-brand-text); background-color: var(--ui-brand-subtle); outline: none; }
.ui-shell .popover {
    --bs-popover-bg: var(--ui-surface);
    --bs-popover-border-color: var(--ui-surface-border);
    --bs-popover-header-bg: var(--ui-surface-muted);
    --bs-popover-header-color: var(--bs-body-color);
    --bs-popover-body-color: var(--bs-body-color);
    --bs-popover-font-size: .8rem;
    --bs-popover-border-radius: var(--bs-border-radius-lg);
    box-shadow: var(--ui-sticky-shadow);
}
.ui-shell .popover-header { font-size: var(--ui-section-title-fs); font-weight: 600; letter-spacing: var(--ui-section-title-ls); text-transform: uppercase; }
/* Teclas: <code> (Bootstrap sanea el contenido del popover y <kbd> no está en su allowList) */
.ui-shell .popover code { font-size: .72rem; padding: .1rem .35rem; border-radius: var(--bs-border-radius-sm); background-color: var(--bs-body-color); color: var(--bs-body-bg); font-family: inherit; font-weight: 600; }

/* Accesibilidad / impresión */
@media (prefers-reduced-motion: reduce) {
    .ui-sidebar, .ui-nav-caret, .ui-collapse-icon, .ui-filter-caret { transition: none; }
    .blink-soft { animation: none; }
}

@media print {
    .ui-sidebar, .ui-topbar, .ui-footer, .ui-panel-foot, .ui-filters { display: none; }
    .ui-content { padding: 0; }
    .ui-panel { border: 0; box-shadow: none; }
}
