/* ============ Identidad corporativa GIA Group ============
   Los colores son personalizables desde el panel 🎨 del navbar:
   --gia-primario / --gia-acento se fijan por JS (components/tema.py).
   Por defecto: azul logo #3A67A7, azul web #0578B0.                */

:root {
    /* Marca (las fija en runtime components/tema.py; NO borrar) */
    --gia-primario: #3A67A7;
    --gia-acento: #0578B0;

    /* Marca derivada */
    --brand-ink: var(--gia-primario);   /* texto/enlace/nav activo sobre claro */
    --brand-tint: color-mix(in srgb, var(--gia-primario) 12%, transparent);
    --brand-hover: color-mix(in srgb, var(--gia-primario) 80%, black);

    /* Superficies */
    --surface-0: #FFFFFF;
    --surface-1: #F5F6F8;
    --surface-2: #FFFFFF;
    --surface-nav: #FFFFFF;

    /* Texto */
    --text-strong: #2B2B2A;
    --text: #4B4B4A;
    --text-muted: #767676;   /* AA sobre blanco (>=4.5:1) */
    --text-on-brand: #FFFFFF;

    /* Borde / sombra / radio */
    --border: #E5E5E5;
    --border-strong: #D0D3D9;
    --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.08);
    --shadow-md: 0 2px 10px rgba(0, 0, 0, 0.10);
    --radius-sm: 6px;
    --radius: 10px;
    --radius-lg: 14px;

    /* Espaciado (escala 4px) */
    --sp-1: 0.25rem;
    --sp-2: 0.5rem;
    --sp-3: 0.75rem;
    --sp-4: 1rem;
    --sp-5: 1.5rem;
    --sp-6: 2rem;

    /* Layout del shell */
    --nav-w: 248px;
    --nav-w-rail: 72px;
    --topbar-h: 56px;
    --filters-w: 280px;

    /* Foco accesible */
    --focus-ring: 0 0 0 3px color-mix(in srgb, var(--gia-acento) 45%, transparent);

    /* Estado (deltas, avisos): reservados, nunca como color de serie.
       --warning es el mismo ambar validado que el segundo color de serie de
       components/charts.py, para no meter un amarillo nuevo en la paleta. */
    --success: #2E7D32;
    --danger: #C62828;
    --warning: #EDA100;
    --info: var(--gia-acento);
    --neutro-ui: #6C7278;

    /* Rojo de RELLENO con texto blanco encima (badges). No es --danger: ese
       se aclara en oscuro para leer como tinta sobre superficie oscura, y un
       relleno claro con texto blanco no pasa contraste. Este no cambia. */
    --danger-solid: #C62828;

    /* Colores de los iconos de stat-card (la "cN" de ui.stat_card): identidad
       decorativa, el dato va siempre en tinta de texto. Tonos tomados de la
       paleta de series validada de components/charts.py. El ambar es el tono
       oscuro (#C98500) porque el claro no aguanta un icono blanco encima. */
    --stat-c1: var(--gia-primario);
    --stat-c2: #1BAF7A;
    --stat-c3: #008300;
    --stat-c4: #4A3AA7;
    --stat-c5: #E34948;
    --stat-c6: #EB6834;
    --stat-c7: #C98500;
    --stat-c8: var(--gia-acento);

    /* Tipografia. La familia y los siete pasos de --fs-* NO se declaran aqui:
       los publica app.py desde config.ESCALA_FS y config.FUENTE al construir
       el indice, porque Plotly necesita NUMEROS y no puede resolver var(), asi
       que la tabla vive en Python y esta hoja la consume. Aqui solo lo que el
       CSS usa y Plotly no: los tamanos de ICONO, que escalan con el area
       tactil y no con la rampa de lectura. */
    --fs-icono: 1rem;
    --fs-icono-lg: 1.25rem;
    --fs-icono-xl: 2rem;

    /* Altura de grafico: FUENTE UNICA. Antes cada grafico la declaraba dos
       veces (en el style del contenedor y en el layout de la figura) y habia
       que mantenerlas a mano; y la que manda de verdad es esta, porque en el
       primer Plots.resize plotly borra layout.height y mide la caja. Cinco
       escalones: las doce alturas que habia eran tres racimos (340-400,
       420-480, 520-600) mas la tira de 220. */
    --graf-h-xs: 220px;
    --graf-h-sm: 300px;
    --graf-h-md: 360px;
    --graf-h-lg: 440px;
    --graf-h-xl: 560px;

    /* Suelo de la pista de la rejilla de graficos (ver .gia-graf-grid). */
    --graf-col-min: 360px;

    /* Bootstrap hereda la familia por aqui, asi que la cogen tambien todos
       los componentes dbc y no solo el body. */
    --bs-body-font-family: var(--font-familia);

    /* ---- Derivados de estado (avisos, badges, pestanas) ----
       El FONDO y el BORDE se declaran una sola vez y se adaptan al tema
       solos: mezclan con --surface-2, que ya cambia por tema, y color-mix se
       resuelve en el elemento donde se usa la variable, no donde se declara.
       La TINTA no puede: sobre claro hay que oscurecer y sobre oscuro
       aclarar, asi que se redeclara en el bloque oscuro. */
    --est-bg-success: color-mix(in srgb, var(--success) 14%, var(--surface-2));
    --est-bg-danger: color-mix(in srgb, var(--danger) 14%, var(--surface-2));
    --est-bg-warning: color-mix(in srgb, var(--warning) 14%, var(--surface-2));
    --est-bg-info: color-mix(in srgb, var(--info) 14%, var(--surface-2));
    --est-bg-neutro: color-mix(in srgb, var(--neutro-ui) 14%, var(--surface-2));

    --est-bd-success: color-mix(in srgb, var(--success) 38%, var(--surface-2));
    --est-bd-danger: color-mix(in srgb, var(--danger) 38%, var(--surface-2));
    --est-bd-warning: color-mix(in srgb, var(--warning) 38%, var(--surface-2));
    --est-bd-info: color-mix(in srgb, var(--info) 38%, var(--surface-2));
    --est-bd-neutro: color-mix(in srgb, var(--neutro-ui) 38%, var(--surface-2));

    --est-ink-success: color-mix(in srgb, var(--success) 55%, black);
    --est-ink-danger: color-mix(in srgb, var(--danger) 55%, black);
    --est-ink-warning: color-mix(in srgb, var(--warning) 40%, black);
    --est-ink-info: color-mix(in srgb, var(--info) 55%, black);
    --est-ink-neutro: color-mix(in srgb, var(--neutro-ui) 55%, black);
}

[data-bs-theme="dark"] {
    --surface-0: #16191d;
    --surface-1: #1c2126;
    --surface-2: #22272e;
    --surface-nav: #1a1f24;

    --text-strong: #F1F3F5;
    --text: #dee2e6;
    --text-muted: #A9B1BA;
    --text-on-brand: #FFFFFF;

    --border: #343a40;
    --border-strong: #495057;
    --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.4);
    --shadow-md: 0 4px 14px rgba(0, 0, 0, 0.5);

    /* Marca aclarada: #3A67A7/#0578B0 no pasan contraste sobre superficie oscura */
    --brand-ink: color-mix(in srgb, var(--gia-primario) 55%, white);
    --brand-tint: color-mix(in srgb, var(--gia-primario) 22%, transparent);

    --success: #6FCF7F;
    --danger: #F28B82;
    --warning: #C98500;
    --info: color-mix(in srgb, var(--gia-acento) 55%, white);
    --neutro-ui: #8A9199;

    /* Solo la tinta: los fondos y bordes de --est-* ya se adaptan porque
       mezclan con --surface-2. Aqui se aclara en vez de oscurecer. */
    --est-ink-success: color-mix(in srgb, var(--success) 80%, white);
    --est-ink-danger: color-mix(in srgb, var(--danger) 80%, white);
    --est-ink-warning: color-mix(in srgb, var(--warning) 65%, white);
    --est-ink-info: color-mix(in srgb, var(--info) 80%, white);
    --est-ink-neutro: color-mix(in srgb, var(--neutro-ui) 80%, white);
}

/* ============ Theming de los dcc de Dash 4 ============
   Dash 4 dibuja Dropdown/DatePicker/Checklist/Input con sus propios tokens
   --Dash-* (los define en <style> inyectados ANTES de este fichero, asi que
   estas redefiniciones ganan por orden de aparicion). Un solo bloque cubre
   claro y oscuro: los var() se resuelven contra el valor vigente del token.
   Imprescindible ademas porque el menu del Dropdown y el calendario viven
   FUERA del nodo con className (.filtro-dd): ningun selector descendiente
   los alcanza, las variables heredadas si. */
:root {
    --Dash-Fill-Interactive-Strong: var(--gia-primario);
    --Dash-Fill-Interactive-Weak: var(--brand-tint);
    --Dash-Fill-Inverse-Strong: var(--surface-2);
    --Dash-Fill-Primary-Hover: var(--brand-tint);
    --Dash-Fill-Primary-Active: color-mix(in srgb, var(--gia-primario) 20%, transparent);
    --Dash-Fill-Disabled: var(--surface-1);
    --Dash-Text-Primary: var(--text);
    --Dash-Text-Strong: var(--text-strong);
    --Dash-Text-Weak: var(--text-muted);
    --Dash-Text-Disabled: var(--text-muted);
    --Dash-Stroke-Strong: var(--border-strong);
    --Dash-Stroke-Weak: var(--border);
}

[data-bs-theme="light"] body,
body {
    color: var(--text);
}

/* ============ Shell del panel admin ============ */
.app-shell {
    display: flex;
    min-height: 100vh;
    background-color: var(--surface-1);
}

.app-main {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
}

.app-body {
    display: flex;
    flex: 1 1 auto;
    align-items: flex-start;
    min-height: 0;
}

.content-col {
    flex: 1 1 auto;
    min-width: 0;
    padding: var(--sp-5);
    background-color: var(--surface-1);
    overflow-x: hidden;
}

.filters-col {
    flex: 0 0 var(--filters-w);
    max-width: var(--filters-w);
}

/* En ESCRITORIO scrollea el contenido, no la ventana: asi el panel de filtros
   se queda quieto mientras se baja por los graficos, que es como se comporta
   Power BI.

   El `position: sticky` que .sidebar ya tenia no servia de nada, por dos
   motivos independientes:
     1. Su contenedor (.filters-col) mide lo mismo que el —con
        `align-items: flex-start` en .app-body no sobra ni un pixel—, y un
        sticky sin hueco en su contenedor no se mueve nunca.
     2. Aunque lo hubiera, cualquier cosa clavada a `top` acaba POR DEBAJO de
        la cinta de chips, que tambien es sticky, es opaca y mide lo que mide
        (envuelve a dos lineas cuando hay muchos filtros activos). No hay
        numero que poner ahi que sea correcto siempre.
   Con el shell a la altura exacta de la ventana y el scroll dentro de cada
   columna, la altura de la cinta NO aparece en ningun calc(): la reparte el
   propio flex. Y de paso la topbar y la cinta quedan fijas de verdad.
   Solo escritorio: en movil las dos columnas se apilan (ver el @media de
   max-width: 991.98px) y ahi el scroll de la pagina es el correcto. */
@media (min-width: 992px) {
    .app-shell {
        height: 100vh;
        overflow: hidden;
    }

    /* min-height: 0 en los dos contenedores intermedios NO es opcional: por
       defecto un hijo flex no puede encogerse por debajo de su contenido, asi
       que .app-body se desbordaria hacia abajo -devolviendo el scroll a la
       ventana- en vez de scrollear por dentro. */
    .app-main {
        min-height: 0;
    }

    .app-body {
        min-height: 0;
        overflow: hidden;
    }

    .filters-col,
    .content-col {
        max-height: 100%;
        overflow-y: auto;
    }

    /* El sticky ya lo hace el contenedor, y aqui estorbaria: dentro de su
       propio scrollport, un `top: var(--topbar-h)` deja al bajar un hueco en
       blanco de la altura de la topbar. */
    .filters-col .sidebar {
        position: static;
        max-height: none;
    }
}

/* ---- Navegacion lateral ---- */
.nav-sidebar {
    flex: 0 0 var(--nav-w);
    width: var(--nav-w);
    flex-direction: column;
    background-color: var(--surface-nav);
    border-right: 1px solid var(--border);
    align-self: flex-start;
    position: sticky;
    top: 0;
    height: 100vh;
    overflow-y: auto;
    overflow-x: hidden;
}

.nav-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-2);
    height: var(--topbar-h);
    padding: 0 var(--sp-3);
    border-bottom: 1px solid var(--border);
    flex: 0 0 auto;
}

.nav-brand {
    display: inline-flex;
    align-items: center;
    overflow: hidden;
}

.nav-logo {
    height: 30px;
    width: auto;
}

[data-bs-theme="dark"] .nav-logo {
    background-color: #f2f2f2;
    border-radius: 4px;
    padding: 2px 6px;
}

.btn-nav-collapse {
    color: var(--text-muted) !important;
    padding: var(--sp-1) var(--sp-2);
    flex: 0 0 auto;
    text-decoration: none;
}

.nav-list {
    list-style: none;
    margin: 0;
    padding: var(--sp-3) var(--sp-2);
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.nav-item {
    list-style: none;
}

.nav-item-link {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    padding: var(--sp-3);
    min-height: 44px;
    border-radius: var(--radius-sm);
    color: var(--text) !important;
    font-weight: 600;
    white-space: nowrap;
    text-decoration: none !important;
    -webkit-user-select: none;
    user-select: none;
}

.nav-item-link:hover,
.nav-item-link:focus-visible {
    background-color: var(--brand-tint);
    color: var(--brand-ink) !important;
}

/* Boton "Detalle" (abre el listado en modal): mismo aspecto que un enlace
   del nav, reseteando el estilo nativo de <button>. */
button.nav-detalle-btn {
    background: none;
    border: 0;
    width: 100%;
    text-align: left;
    font: inherit;
    cursor: pointer;
}

/* Tras hacer clic con el raton el enlace no debe quedarse "seleccionado" */
.nav-item-link:focus:not(:focus-visible):not(.active),
.nav-group-summary:focus:not(:focus-visible) {
    background-color: transparent;
    color: var(--text) !important;
}

/* Item activo: indicador sutil (borde + tinta), no pildora rellena */
.nav-item-link.active {
    background-color: color-mix(in srgb, var(--gia-primario) 7%, transparent);
    border-left: 3px solid var(--gia-primario);
    border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
    color: var(--brand-ink) !important;
}

.nav-item-icon {
    font-size: var(--fs-icono);
    flex: 0 0 auto;
    width: 1.25em;
    text-align: center;
}

/* Grupos desplegables del nav (<details>/<summary>) */
.nav-group {
    list-style: none;
}

.nav-group-summary {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    padding: var(--sp-3);
    min-height: 44px;
    border-radius: var(--radius-sm);
    color: var(--text) !important;
    font-weight: 600;
    white-space: nowrap;
    cursor: pointer;
    list-style: none;
    -webkit-user-select: none;
    user-select: none;
}

.nav-group-summary::-webkit-details-marker {
    display: none;
}

.nav-group-summary::marker {
    content: "";
}

.nav-group-summary:hover,
.nav-group-summary:focus-visible {
    background-color: var(--brand-tint);
    color: var(--brand-ink) !important;
}

.nav-group-chevron {
    margin-left: auto;
    font-size: var(--fs-xs);
    transition: transform 0.15s ease;
}

.nav-group[open] > .nav-group-summary .nav-group-chevron {
    transform: rotate(90deg);
}

.nav-sublist {
    list-style: none;
    margin: 2px 0 var(--sp-2);
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 2px;
}

/* Rotulo dentro de un grupo del menu (Pagina.encabezado: "Ubicación GPS"
   dentro de Personal). Alineado con el TEXTO de los enlaces, no con su
   icono. */
.nav-subgrupo {
    list-style: none;
    padding: var(--sp-2) var(--sp-3) 2px calc(var(--sp-3) + 1.25em + var(--sp-3));
}

.nav-subgrupo-rotulo {
    display: block;
    font-size: var(--fs-2xs);
    font-weight: 700;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--text-muted);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Barra contraida: el rotulo queda como una raya entre bloques (la primera
   sobra) y el texto solo para lectores de pantalla. */
.app-shell[data-nav-collapsed="1"] .nav-subgrupo {
    padding: 0;
    margin: var(--sp-1) var(--sp-3);
    border-top: 1px solid var(--border);
}

.app-shell[data-nav-collapsed="1"] .nav-subgrupo:first-child {
    display: none;
}

.app-shell[data-nav-collapsed="1"] .nav-subgrupo-rotulo {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

.nav-sub-link.nav-item-link {
    padding-left: calc(var(--sp-3) + 1.25em + var(--sp-3));
    font-weight: 500;
}

/* Rail colapsado: grupos como iconos, sin chevron ni sangria */
.app-shell[data-nav-collapsed="1"] .nav-group-chevron {
    display: none;
}

.app-shell[data-nav-collapsed="1"] .nav-group-summary {
    justify-content: center;
    gap: 0;
    padding-left: 0;
    padding-right: 0;
}

.app-shell[data-nav-collapsed="1"] .nav-sub-link.nav-item-link {
    padding-left: 0;
}

/* Rail colapsado (solo escritorio) */
.app-shell[data-nav-collapsed="1"] .nav-sidebar {
    flex-basis: var(--nav-w-rail);
    width: var(--nav-w-rail);
}

.app-shell[data-nav-collapsed="1"] .nav-brand {
    display: none;
}

/* Rail: oculta la etiqueta visualmente pero la mantiene para lectores de
   pantalla (nombre accesible del enlace); el tooltip lo da el title. */
.app-shell[data-nav-collapsed="1"] .nav-item-label {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

.app-shell[data-nav-collapsed="1"] .nav-header {
    justify-content: center;
}

.app-shell[data-nav-collapsed="1"] .nav-item-link {
    justify-content: center;
    gap: 0;
    padding-left: 0;
    padding-right: 0;
}

.app-shell[data-nav-collapsed="1"] .btn-nav-collapse i {
    transform: rotate(180deg);
    display: inline-block;
}

/* ---- Topbar ---- */
.topbar {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    height: var(--topbar-h);
    padding: 0 var(--sp-4);
    background-color: var(--surface-0);
    border-bottom: 1px solid var(--border);
    box-shadow: var(--shadow-sm);
    position: sticky;
    top: 0;
    z-index: 1020;
}

.topbar-title {
    font-weight: 700;
    font-size: var(--fs-lg);
    color: var(--text-strong);
    /* Puede encogerse y truncar para no empujar las acciones fuera de pantalla */
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.topbar-brand img {
    display: block;
}

[data-bs-theme="dark"] .topbar-brand img {
    background-color: #f2f2f2;
    border-radius: 4px;
    padding: 2px 6px;
}

.topbar-actions {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    margin-left: auto;
}

.topbar-user {
    color: var(--text-muted);
    font-size: var(--fs-md);
    display: inline-flex;
    align-items: center;
}

/* La identidad es un enlace a /mi-cuenta: sin subrayado y sin color de enlace. */
a.topbar-user {
    text-decoration: none;
}

a.topbar-user:hover,
a.topbar-user:focus-visible {
    color: var(--brand-ink);
}

.topbar-logout {
    color: var(--brand-ink) !important;
    text-decoration: none;
    font-weight: 600;
    display: inline-flex;
    align-items: center;
    padding: var(--sp-2) var(--sp-3);
    min-height: 44px;
    border-radius: var(--radius-sm);
}

.topbar-logout:hover {
    background-color: var(--brand-tint);
}

/* Vista previa de administrador ("Ver como", components/navbar.py). El rojo es
   deliberado: mientras esta puesto, lo que se ve NO son tus permisos. */
.vercomo-dd {
    min-width: 200px;
}

.vercomo-cinta {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-2);
    padding: var(--sp-1) var(--sp-3);
    border: 1px solid var(--danger);
    border-radius: var(--radius-sm);
    background-color: color-mix(in srgb, var(--danger) 12%, transparent);
    color: var(--text-strong);
    font-size: var(--fs-md);
    white-space: nowrap;
}

.vercomo-salir {
    color: var(--danger) !important;
    text-decoration: none;
    font-weight: 600;
    display: inline-flex;
    align-items: center;
    padding: var(--sp-1) var(--sp-2);
    min-height: 32px;
    border-radius: var(--radius-sm);
}

.vercomo-salir:hover,
.vercomo-salir:focus-visible {
    background-color: color-mix(in srgb, var(--danger) 18%, transparent);
}

/* Marca de agua en toda la barra: que no se pueda olvidar (app.py fija el
   atributo en #app-shell desde serve_layout). */
.app-shell[data-vercomo="1"] .topbar {
    box-shadow: inset 0 -3px 0 var(--danger);
}

.btn-nav-toggle {
    color: var(--text) !important;
    font-size: var(--fs-icono-lg);
    padding: var(--sp-1) var(--sp-2);
    text-decoration: none;
}

/* Enlace de salto al contenido (accesibilidad) */
.skip-link {
    position: absolute;
    left: -9999px;
    top: 0;
    z-index: 2000;
    padding: var(--sp-2) var(--sp-4);
    background: var(--gia-primario);
    color: var(--text-on-brand) !important;
    border-radius: 0 0 var(--radius-sm) 0;
}

.skip-link:focus {
    left: 0;
}

.btn-tema {
    border: none;
    font-size: var(--fs-icono);
}

/* ---- Visibilidad por seccion (data-seccion lo fija el clientside de ruta) ----
   Panel de filtros: en las secciones que consumen el Store global-filters
   (principal, albaranes, clientes, canales, articulos) y en SAT (que muestra
   en el mismo panel su bloque propio .solo-sat, ver abajo). Catalogo y
   almacenes llevan barra propia y no leen ese Store, por eso no lo muestran.
   content-col (flex:1) ocupa el ancho sobrante cuando el panel no esta. */
.filters-col {
    display: none;
}

body[data-seccion="principal"] .filters-col,
body[data-seccion="albaranes"] .filters-col,
body[data-seccion="clientes"] .filters-col,
body[data-seccion="canales"] .filters-col,
body[data-seccion="articulos"] .filters-col,
body[data-seccion="sat"] .filters-col {
    display: block;
}

/* Ocultado manual del panel de filtros (boton embudo / boton del panel) */
.app-shell[data-filters-hidden="1"] .filters-col {
    display: none !important;
}

/* Boton embudo (mostrar/ocultar filtros): donde hay panel lateral */
.btn-filtros-toggle {
    display: none;
    color: var(--text) !important;
    font-size: var(--fs-icono-lg);
    padding: var(--sp-1) var(--sp-2);
    text-decoration: none;
}

body[data-seccion="principal"] .btn-filtros-toggle,
body[data-seccion="albaranes"] .btn-filtros-toggle,
body[data-seccion="clientes"] .btn-filtros-toggle,
body[data-seccion="canales"] .btn-filtros-toggle,
body[data-seccion="articulos"] .btn-filtros-toggle,
body[data-seccion="sat"] .btn-filtros-toggle {
    display: inline-flex;
    align-items: center;
}

/* Botones del listado en modal (Excel + reset de filtros): en toda seccion
   con listado (lista propia, NO la del embudo: aqui entran tambien las
   secciones con barra de filtros propia). Ocultos quedan datos, mi-cuenta
   y "otro" (login, 404). El d-none del reset lo alterna un callback segun
   haya o no filtros activos en la seccion. */
.btn-listado-toggle,
.btn-listado-reset {
    display: none;
    color: var(--text) !important;
    font-size: var(--fs-icono-lg);
    padding: var(--sp-1) var(--sp-2);
    text-decoration: none;
    position: relative;
}

body[data-seccion="principal"] .btn-listado-toggle,
body[data-seccion="albaranes"] .btn-listado-toggle,
body[data-seccion="clientes"] .btn-listado-toggle,
body[data-seccion="canales"] .btn-listado-toggle,
body[data-seccion="articulos"] .btn-listado-toggle,
body[data-seccion="sat"] .btn-listado-toggle,
body[data-seccion="compras"] .btn-listado-toggle,
body[data-seccion="almacenes"] .btn-listado-toggle,
body[data-seccion="visitas"] .btn-listado-toggle,
body[data-seccion="transitarios"] .btn-listado-toggle,
body[data-seccion="catalogo"] .btn-listado-toggle,
body[data-seccion="litigios"] .btn-listado-toggle,
body[data-seccion="presupuestos"] .btn-listado-toggle,
body[data-seccion="logistica"] .btn-listado-toggle,
body[data-seccion="fgas"] .btn-listado-toggle,
body[data-seccion="pagos"] .btn-listado-toggle,
body[data-seccion="siniestros"] .btn-listado-toggle,
body[data-seccion="contenedores"] .btn-listado-toggle,
body[data-seccion="principal"] .btn-listado-reset,
body[data-seccion="albaranes"] .btn-listado-reset,
body[data-seccion="clientes"] .btn-listado-reset,
body[data-seccion="canales"] .btn-listado-reset,
body[data-seccion="articulos"] .btn-listado-reset,
body[data-seccion="sat"] .btn-listado-reset,
body[data-seccion="compras"] .btn-listado-reset,
body[data-seccion="almacenes"] .btn-listado-reset,
body[data-seccion="visitas"] .btn-listado-reset,
body[data-seccion="transitarios"] .btn-listado-reset,
body[data-seccion="catalogo"] .btn-listado-reset,
body[data-seccion="litigios"] .btn-listado-reset,
body[data-seccion="presupuestos"] .btn-listado-reset,
body[data-seccion="logistica"] .btn-listado-reset,
body[data-seccion="fgas"] .btn-listado-reset,
body[data-seccion="pagos"] .btn-listado-reset,
body[data-seccion="siniestros"] .btn-listado-reset,
body[data-seccion="contenedores"] .btn-listado-reset {
    display: inline-flex;
    align-items: center;
}

/* Badge del boton Excel: numero de filtros del listado activos */
.listado-badge {
    position: absolute;
    top: 0;
    right: 0;
    font-size: var(--fs-2xs);
    line-height: 1;
    padding: 2px 4px;
    border-radius: 8px;
    background: var(--danger-solid);
    color: var(--text-on-brand);
}

/* Modal del listado: altura de viewport EXPLICITA en el contenedor del grid.
   Nada de cadenas height:100% a traves del wrapper de dcc.Loading: el
   porcentaje contra un flex sin altura definida colapsa a ~0 y el modal se
   ve en blanco (verificado midiendo el DOM). 134px = cabecera del modal
   (~67) + padding del body (24) + margen del aviso de export + el pie de
   recuento de la tabla (components/tablas.pie, ~24). */
.modal-listado .modal-body {
    padding: var(--sp-3);
    overflow: hidden;
}

.modal-listado .listado-grid-wrap {
    height: calc(100vh - 134px);
    min-height: 240px;
}

/* dcc.Loading en Dash 4 interpone DOS divs (en Dash 3 era uno): ambos
   eslabones necesitan el 100% para que el grid no colapse. */
.modal-listado .listado-grid-wrap > div,
.modal-listado .listado-grid-wrap > div > div {
    height: 100%;
}

.modal-listado-objeto {
    margin-left: var(--sp-3);
    font-size: var(--fs-sm);
    font-weight: 400;
    color: var(--text-muted);
}

.btn-filtros-collapse {
    color: var(--text-muted) !important;
    text-decoration: none;
}

/* ---- Barra de chips de filtros rapidos ---- */
.topfilters {
    display: none;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-1) var(--sp-3);
    padding: var(--sp-2) var(--sp-4);
    background-color: var(--surface-0);
    border-bottom: 1px solid var(--border);
    position: sticky;
    top: var(--topbar-h);
    z-index: 1010;
}

body[data-seccion="principal"] .topfilters,
body[data-seccion="clientes"] .topfilters,
body[data-seccion="canales"] .topfilters,
body[data-seccion="albaranes"] .topfilters,
body[data-seccion="articulos"] .topfilters,
body[data-seccion="sat"] .topfilters {
    display: flex;
}

/* Contenido por seccion dentro del panel y de la barra: el informe SAT
   comparte panel/chips con el resto, pero muestra sus propios controles
   (.solo-sat) y oculta los del informe de ventas (.solo-global). */
.topfilters .solo-sat,
.sidebar .solo-sat {
    display: none;
}

body[data-seccion="sat"] .topfilters .topf-grupo.solo-sat {
    display: flex;
}

body[data-seccion="sat"] .topfilters .chip.solo-sat {
    display: inline-block;
}

body[data-seccion="sat"] .sidebar .solo-sat {
    display: block;
}

body[data-seccion="sat"] .topfilters .solo-global,
body[data-seccion="sat"] .sidebar .solo-global {
    display: none;
}

/* Seccion Personal: en la cinta solo va el filtro de persona
   (components/filtro_persona.py), y nada a quien no tiene ninguna pagina de
   ubicacion GPS. */
body[data-seccion="personal"] .topfilters {
    display: flex;
}

.topfilters .solo-personal {
    display: none;
}

body[data-seccion="personal"] .topfilters .topf-grupo.solo-personal {
    display: flex;
}

body[data-seccion="personal"] .topfilters .solo-global {
    display: none;
}

.topfilters .topf-grupo.prs-sin-permiso {
    display: none !important;
}

/* Alto de la cinta, para las paginas de alto fijo de Personal:
   calc(100dvh - N - var(--cinta-h)). En movil la cinta no es sticky y la
   pagina scrollea: no resta. */
:root {
    --cinta-h: 0px;
}

@media (min-width: 992px) {
    body[data-seccion="personal"] {
        --cinta-h: 51px;   /* medida en el visor a 1400 px */
    }
}

/* Dash 4: el className cae en el BOTON; el ancho lo manda el wrapper. Con
   un width explicito y sin wrap: con solo flex-basis el grupo calculaba su
   ancho por el contenido del boton y partia el rotulo y el desplegable en
   dos lineas (cinta de 79 px en vez de 51). */
.topf-persona {
    flex-wrap: nowrap;
}

.topf-persona .dash-dropdown-wrapper {
    flex: 0 1 380px;
    width: 380px;
    min-width: 240px;
}

@media (max-width: 991.98px) {
    .topf-persona {
        flex-wrap: nowrap;
        width: 100%;
    }

    .topf-persona .dash-dropdown-wrapper {
        flex: 1 1 auto;
        width: auto;
        min-width: 0;
    }
}

.topf-grupo {
    display: flex;
    align-items: center;
    gap: var(--sp-1);
    flex-wrap: wrap;
}

.topf-label {
    text-transform: uppercase;
    letter-spacing: 0.04em;
    font-size: var(--fs-2xs);
    font-weight: 700;
    color: var(--text-muted);
    margin-right: var(--sp-1);
}

.chip {
    border-radius: 999px;
    border: 1px solid var(--border);
    background-color: var(--surface-2);
    color: var(--text);
    font-size: var(--fs-xs);
    font-weight: 600;
    padding: 0.2rem 0.7rem;
    min-height: 30px;
    line-height: 1.1;
    cursor: pointer;
}

.chip:hover {
    border-color: var(--gia-primario);
    color: var(--brand-ink);
}

.chip.active,
.chip.active:hover {
    background-color: var(--gia-primario);
    border-color: var(--gia-primario);
    color: var(--text-on-brand);
}

.chip:focus:not(:focus-visible) {
    outline: none;
}

/* Limpiar filtros: alineado a la derecha, tono discreto */
.chip-limpiar {
    margin-left: auto;
    color: var(--text-muted);
    border-style: dashed;
}

.chip-limpiar:hover {
    color: var(--danger);
    border-color: var(--danger);
}

/* ---- Botones primarios (Aplicar filtros, exportar...) ---- */
.btn-primary {
    background-color: var(--gia-primario) !important;
    border-color: var(--gia-primario) !important;
}

.btn-primary:hover,
.btn-primary:active,
.btn-primary:focus {
    background-color: color-mix(in srgb, var(--gia-primario) 80%, black) !important;
    border-color: color-mix(in srgb, var(--gia-primario) 80%, black) !important;
}

.btn-link {
    color: var(--brand-ink) !important;
}

a {
    color: var(--brand-ink);
}

/* ---- Sidebar de filtros ---- */
.sidebar {
    background-color: var(--surface-1);
    border-right: 1px solid var(--border);
    font-size: var(--fs-md);
    position: sticky;
    top: var(--topbar-h);
    max-height: calc(100vh - var(--topbar-h));
    overflow-y: auto;
    /* El sticky crea un contexto de apilamiento: sin z-index propio, los
       popovers Radix de los dcc (calendario del DatePickerRange, 524px)
       que sobresalen del panel quedan DEBAJO de las tarjetas con graficos
       por orden de documento. Con z-index bajo basta (topbar en 1020). */
    z-index: 3;
}

.sidebar .form-check-input:checked {
    background-color: var(--gia-primario);
    border-color: var(--gia-primario);
}

/* DatePickerRange (Dash 4; el del panel lateral y el de la barra de
   pages/gps_recorridos.py): por defecto el wrapper es display:block con los
   inputs en flujo inline y ancho fijado como estilo inline por un medidor de
   texto (que ademas mide 0 si el bloque se monta oculto, caso de
   f-bloque-fechas). Con otra fuente o zoom el flujo inline PARTE las dos
   fechas en dos lineas y la caja se hincha. Flex nowrap con reparto
   equitativo (como el CSS de react-dates en Dash 3): no puede envolver a
   ningun tamano de fuente. Los !important ganan a los estilos inline del
   componente. Colores via tokens --Dash-*. */
.sidebar .dash-datepicker-input-wrapper,
.gps-barra .dash-datepicker-input-wrapper {
    display: flex;
    align-items: center;
    flex-wrap: nowrap;
    gap: 2px;
    width: 100%;
    box-sizing: border-box;
    padding: 0 8px;
}

.sidebar .dash-datepicker-input-wrapper > div,
.gps-barra .dash-datepicker-input-wrapper > div {
    flex: 1 1 0;
    min-width: 0;
}

.sidebar .dash-datepicker-input-wrapper > svg,
.gps-barra .dash-datepicker-input-wrapper > svg {
    flex: 0 0 auto;
}

.sidebar .dash-datepicker-input,
.gps-barra .dash-datepicker-input {
    font-size: var(--fs-sm);
    text-align: center;
    width: 100% !important;
    box-sizing: border-box !important;
    min-width: 0;
    padding: 4px 2px;
}

/* El popover del calendario a veces abre con la caja pintada mas estrecha
   que los dos meses (estado transitorio de Radix) y el segundo mes queda
   transparente sobre la pagina: fondo opaco en el propio calendario. */
.dash-datepicker-calendar-wrapper {
    background: var(--surface-2);
}

.btn-atajos .btn {
    font-size: var(--fs-xs);
}

/* Dropdown (Dash 4): el className cae en el propio boton disparador, la
   fuente compacta se hereda a los spans interiores. El menu de opciones
   cuelga de un wrapper hermano (fuera de .filtro-dd): sus colores llegan
   por los tokens --Dash-* del bloque de identidad, tambien en oscuro. */
.filtro-dd {
    font-size: var(--fs-sm);
}

/* Desplegable de mes en la barra de filtros: ancho compacto y sin desbordar */
.topf-dd-mes {
    min-width: 130px;
}

/* ============ Accesibilidad ============ */
/* Foco visible por teclado (no habia ninguna regla propia hasta ahora) */
:focus-visible {
    outline: 2px solid transparent;
    outline-offset: 2px;
    box-shadow: var(--focus-ring);
}

/* No mostrar el anillo con foco de raton/tactil */
:focus:not(:focus-visible) {
    outline: none;
    box-shadow: none;
}

/* Targets tactiles minimos (WCAG 2.5.5) */
.btn-tema,
.btn-nav-toggle {
    min-width: 44px;
    min-height: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}

/* Respeta la preferencia de movimiento reducido */
@media (prefers-reduced-motion: no-preference) {
    .nav-sidebar {
        transition: flex-basis 0.18s ease, width 0.18s ease;
    }

    .nav-item-link,
    .topbar-logout,
    .btn-primary {
        transition: background-color 0.15s ease, color 0.15s ease;
    }
}

/* ============ Responsive ============ */
/* <992px: la navegacion va en el drawer (d-lg-flex/d-lg-none lo gestionan).
   Los filtros pasan a apilarse sobre el contenido, con scroll propio para no
   empujarlo fuera de la pantalla. */
@media (max-width: 991.98px) {
    .app-body {
        flex-direction: column;
        /* En columna el eje transversal es el ancho: sin stretch los hijos se
           encogen/crecen a su contenido y content-col se desborda del viewport
           (causa del scroll horizontal en movil). */
        align-items: stretch;
    }

    .content-col {
        width: 100%;
        padding: var(--sp-3);
    }

    .filters-col {
        flex: 0 0 auto;
        max-width: none;
        width: 100%;
    }

    /* Defecto en movil/tablet: apilado sobre el contenido, el panel se comia
       la primera pantalla completa y el usuario aterrizaba en 366px de
       filtros sin ver un solo dato. "auto" = todavia no ha decidido nada
       (ver el tri-estado de app.py); el embudo sigue abriendolo y su
       eleccion persiste en localStorage. */
    .app-shell[data-filters-hidden="auto"] .filters-col {
        display: none !important;
    }

    .filters-col .sidebar {
        position: static;
        /* dvh y no vh: en movil vh cuenta la barra de URL retraida, asi que
           45vh reservaba mas alto del que hay de verdad. El vh queda como
           respaldo para navegadores sin dvh (sin el, la declaracion se
           descarta y el panel crece sin tope). */
        max-height: 45vh;
        max-height: 60dvh;
        overflow-y: auto;
        border-right: none;
        border-bottom: 1px solid var(--border);
    }

    /* Atajos de fecha: targets mas comodos al tacto */
    .btn-atajos .btn {
        min-height: 40px;
    }

    /* Drawer de navegacion: ancho acotado para que el backdrop quede visible y
       se lea como una capa por encima (no una pagina nueva a pantalla completa). */
    .nav-drawer.offcanvas {
        --bs-offcanvas-width: min(320px, 85vw);
    }
}

/* Tablet/movil (nav en drawer): compactar la topbar para que las acciones
   quepan sin desbordarse. */
@media (max-width: 767.98px) {
    .topbar {
        padding: 0 var(--sp-3);
        gap: var(--sp-2);
    }

    /* La identidad del usuario ya aparece en el pie del drawer: en la topbar
       movil solo estorba y ocupa espacio. */
    .topbar-user {
        display: none;
    }

    /* Logout: solo el icono (el texto "Salir" se queda para lectores de pantalla) */
    .topbar-logout .topbar-logout-label {
        position: absolute;
        width: 1px;
        height: 1px;
        padding: 0;
        margin: -1px;
        overflow: hidden;
        clip: rect(0, 0, 0, 0);
        white-space: nowrap;
        border: 0;
    }

    .topbar-logout .bi {
        margin-right: 0 !important;
    }
}

@media (max-width: 575.98px) {
    /* En pantallas muy estrechas el logo de la marca cede su sitio al titulo */
    .topbar-brand.d-lg-none {
        display: none !important;
    }
}

/* ---- Barra de chips (topfilters) en movil ---- */
/* Apilada en filas por grupo con scroll horizontal propio: evita que envuelva
   en un bloque alto y pegajoso que se coma la pantalla del telefono. */
@media (max-width: 991.98px) {
    .topfilters {
        position: static;
        flex-direction: column;
        /* nowrap: en columna, wrap crearia multi-columnas y estiraria los
           grupos a su contenido (desbordando la pagina). */
        flex-wrap: nowrap;
        align-items: stretch;
        gap: var(--sp-2);
        padding: var(--sp-2) var(--sp-3);
    }

    .topf-grupo {
        flex-wrap: nowrap;
        /* min-width:0 permite que el grupo se encoja al ancho del contenedor;
           sin el, min-width:auto lo estira a su contenido y desborda la pagina. */
        min-width: 0;
        overflow-x: auto;
        -webkit-overflow-scrolling: touch;
        scrollbar-width: thin;
        padding-bottom: 3px;
    }

    .topf-label {
        position: sticky;
        left: 0;
        flex: 0 0 auto;
        background-color: var(--surface-0);
        padding-right: var(--sp-2);
        z-index: 1;
    }

    .topf-grupo .chip {
        flex: 0 0 auto;
    }

    .chip-limpiar {
        margin-left: 0;
        align-self: flex-start;
    }
}

/* ============ Interior de paginas ============ */
.page-header {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: var(--sp-4);
    margin-bottom: var(--sp-4);
    flex-wrap: wrap;
}

.page-header-titulo {
    font-size: var(--fs-2xl);
    font-weight: 700;
    color: var(--text-strong);
    margin: 0;
}

.page-header-subtitulo {
    color: var(--text-muted);
    margin: 2px 0 0;
    font-size: var(--fs-md);
}

.page-header-acciones {
    display: flex;
    gap: var(--sp-2);
    align-items: center;
    flex-wrap: wrap;
}

.section-header {
    font-size: var(--fs-lg);
    font-weight: 700;
    color: var(--text-strong);
    text-transform: uppercase;
    letter-spacing: 0.03em;
    margin: var(--sp-2) 0 var(--sp-3);
}

/* Tarjeta unificada del panel */
.gia-card {
    background-color: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow-sm);
    padding: var(--sp-4);
    height: 100%;
}

.gia-card-cabecera {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-3);
    margin-bottom: var(--sp-2);
}

/* En el telefono, una cabecera con varios controles (horizonte, grano,
   escenario...) no cabe al lado del titulo: los controles bajan de linea en
   vez de desbordar la tarjeta. */
@media (max-width: 575.98px) {
    .gia-card-cabecera {
        flex-wrap: wrap;
    }
}

.gia-card-titulo {
    font-size: var(--fs-lg);
    font-weight: 700;
    color: var(--text-strong);
    margin: 0;
}

.gia-card-acciones {
    display: flex;
    gap: var(--sp-2);
    align-items: center;
    flex-wrap: wrap;
}

/* Cuerpo de la tarjeta: existe como punto de engarce del body_class de
   ui.card (lo emiten las 232 tarjetas y no tenia ninguna regla). min-width:0
   deja que un hijo ancho (rejilla, tabla, grid) se encoja en vez de estirar
   la tarjeta y desbordar la pagina. */
.gia-card-cuerpo {
    min-width: 0;
}

/* ============ Graficos: la altura la manda el contenedor ============
   Cadena de DOS divs, y los dos hacen falta:

     div.gia-grafico.gia-graf-md      <- contenedor de consulta (inline-size)
       div, div  (dcc.Loading)        <- altura auto, no se puede evitar
         div.gia-grafico-caja         <- ALTURA (objetivo de las @container)
           div.dash-graph             <- height:100%, lo inyecta Dash

   La caja va DENTRO del dcc.Loading a proposito: sus dos divs son de altura
   auto, asi que una altura puesta por encima no llega y el height:100% que
   Dash inyecta se resuelve contra un auto -> computa auto -> plotAutoSize lo
   rechaza y cae a los 450px de defecto de plotly. Ese era el bug de los 46
   graficos sin altura.
   Y el contenedor no puede ser la misma caja: una @container solo casa
   DESCENDIENTES del contenedor, nunca el contenedor mismo.

   El container-type va SOLO aqui, donde dentro no hay mas que el Loading y
   el grafico. En .gia-card no puede ir: implica contain:layout, que convierte
   al elemento en bloque contenedor de sus descendientes position:fixed, y el
   menu del Dropdown de Dash 4 es un popper de Radix con estrategia fixed que
   NO se porta a document.body (ver el comentario del Dropdown mas arriba);
   vive en ui.card(acciones=...) en ocho paginas. En .content-col tampoco: hay
   dbc.Modal dentro del contenido de pagina.

   SIN overflow: contain:layout no implica contencion de pintado, asi que las
   hoverlabels de plotly pueden salir del area de trazado. Recortarlas seria
   recortar datos. */
.gia-grafico {
    container: grafico / inline-size;
}

.gia-grafico-caja {
    height: var(--graf-h, var(--graf-h-md));
}

/* Cada escalon declara su altura base, a cual BAJA en tarjeta estrecha y a
   cual SUBE en tarjeta ancha, siempre moviendose un peldano de la misma
   escala. Las custom properties heredan, y la caja es descendiente del
   contenedor, que es lo que exige @container.
   Solo dos saltos, sin un tercero para monitores muy anchos: asi la pagina no
   se alarga por tener pantalla grande. El precio asumido es que un grafico a
   fila completa por encima de 1600px sigue siendo plano. */
/* En la zona personalizada el escalon lo decide la CELDA (.zona-alto-*), que
   es lo que el usuario cambia al redimensionar: asi cambiar el alto es cambiar
   una clase y no repintar la figura. Gana por especificidad (0,2,0) al
   .gia-graf-* que trae el grafico. */
.gia-graf-xs, .zona-alto-xs .gia-grafico { --graf-h: var(--graf-h-xs); --graf-h-corta: var(--graf-h-xs); --graf-h-ancha: var(--graf-h-xs); }
.gia-graf-sm, .zona-alto-sm .gia-grafico { --graf-h: var(--graf-h-sm); --graf-h-corta: var(--graf-h-xs); --graf-h-ancha: var(--graf-h-md); }
.gia-graf-md, .zona-alto-md .gia-grafico { --graf-h: var(--graf-h-md); --graf-h-corta: var(--graf-h-sm); --graf-h-ancha: var(--graf-h-lg); }
.gia-graf-lg, .zona-alto-lg .gia-grafico { --graf-h: var(--graf-h-lg); --graf-h-corta: var(--graf-h-md); --graf-h-ancha: var(--graf-h-xl); }
.gia-graf-xl, .zona-alto-xl .gia-grafico { --graf-h: var(--graf-h-xl); --graf-h-corta: var(--graf-h-lg); --graf-h-ancha: var(--graf-h-xl); }

/* Umbrales en los HUECOS entre los anchos de envoltorio que produce el shell
   (317/345/367/399 · 485/622 · 741/758/1021 · 1295/1566), no en numeros
   redondos: asi 20px de barra de scroll o de padding no cambian de escalon. */
@container grafico (max-width: 420px) {
    .gia-grafico-caja { height: var(--graf-h-corta); }
}

@container grafico (min-width: 680px) {
    .gia-grafico-caja { height: var(--graf-h-ancha); }
}

/* Rejilla de graficos: 1 o 2 columnas segun el ancho REAL del contenedor, sin
   container queries. El suelo del 48% en la funcion MIN de la pista garantiza
   el techo de 2 columnas a cualquier ancho: con N=3 y W>=750 saldria
   1,44*W + 2*gap <= W, imposible; y por debajo de 750, 3*360 tampoco cabe.
   Requiere gap <= 0,04*W, que con 16px se cumple desde W=400.
   El min(100%, ...) no es decorativo: sin el, cuando el contenedor baja de
   360px la pista mide mas que el contenedor y el overflow-x:hidden de
   .content-col recorta EN SILENCIO, sin generar scroll y sin que lo cace
   ninguna prueba. .stat-grid no lo necesita solo porque su suelo (210px)
   nunca supera al contenedor mas estrecho del panel (317px). */
.gia-graf-grid {
    display: grid;
    gap: var(--sp-4);
    grid-template-columns:
        repeat(auto-fit, minmax(min(100%, max(var(--graf-col-min), 48%)), 1fr));
}

/* min-width:auto en un item de rejilla lo estira a su contenido minimo. */
.gia-graf-grid > * {
    min-width: 0;
}

/* Por debajo de lg el shell se apila y el contenido recupera el viewport
   entero: 744px en una tablet de 768 darian dos pistas de 364, mas estrechas
   que el peor caso de escritorio. Ahi manda el dispositivo. */
@media (max-width: 991.98px) {
    .gia-graf-grid {
        grid-template-columns: 1fr;
    }
}

/* ============================== MODALES ==================================
   Hasta ahora el panel NO tenia ni una regla de modal: los siete se pintaban
   con el tema de Bootstrap tal cual, asi que ni el fondo, ni el radio, ni los
   bordes salian del sistema de diseno y desentonaban con las tarjetas. Estas
   cuatro reglas los traen a casa, y afectan a todos por igual. */
.modal-content {
    background-color: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-md);
}

.modal-header,
.modal-footer {
    border-color: var(--border);
}

/* El pie en la superficie de pagina separa los botones del contenido sin una
   linea mas, que es como lo resuelven las tarjetas del panel. */
.modal-footer {
    background-color: var(--surface-1);
    border-bottom-left-radius: var(--radius-lg);
    border-bottom-right-radius: var(--radius-lg);
}

.modal-title {
    color: var(--text-strong);
    font-size: var(--fs-lg);
    font-weight: 600;
}

/* ============================== POPOVERS =================================
   El mismo caso que los modales: Bootstrap los pinta con --bs-body-bg, que en
   el panel es --surface-1 (el fondo de PAGINA, no el de tarjeta), y con la
   fuente de Flatly (--bs-font-sans-serif) en vez de la del panel. El primero
   fue el de los perfiles de cada usuario en /permisos. */
.popover {
    --bs-popover-bg: var(--surface-2);
    --bs-popover-border-color: var(--border-strong);
    --bs-popover-border-radius: var(--radius);
    --bs-popover-inner-border-radius: calc(var(--radius) - 1px);
    --bs-popover-font-size: var(--fs-md);
    --bs-popover-header-bg: var(--surface-1);
    --bs-popover-header-color: var(--text-strong);
    --bs-popover-header-font-size: var(--fs-md);
    --bs-popover-body-color: var(--text);
    --bs-popover-body-padding-y: var(--sp-3);
    font-family: var(--font-familia);
    box-shadow: var(--shadow-md);
}

.popover-header {
    font-weight: 600;
}

/* ========================= ZONA PERSONALIZADA =============================
   Lienzo de paneles propios (pages/zona_panel.py).

   Rejilla PROPIA de 12 columnas y no .gia-graf-grid: aquella es auto-fit con
   suelo de 360px y techo de dos pistas, y no sabe nada del ancho que el
   usuario le haya dado a CADA tarjeta. Aqui el ancho es un dato guardado
   (dbo.DashboardsGraficos.Ancho), asi que hacen falta pistas fijas.

   Sin grid-auto-flow: dense a proposito. Taparia los huecos que deja una
   tarjeta ancha reordenando VISUALMENTE respecto al DOM, y entonces los
   botones "mover antes / mover despues" dejarian de corresponderse con lo que
   se ve (y WCAG 1.3.2 con ellos). Un hueco a la derecha es el precio correcto;
   Power BI hace lo mismo. */
/* La zona se apoya en blanco y no en el gris de pagina. Las tarjetas siguen
   distinguiendose por su borde y su sombra, que es como se lee un panel de BI:
   baldosas sobre una hoja. surface-0 y no surface-2 para que en modo oscuro la
   pagina quede POR DEBAJO de las tarjetas, no al mismo nivel. */
body[data-seccion="zona"] .content-col {
    background-color: var(--surface-0);
}

.zona-lienzo {
    display: grid;
    grid-template-columns: repeat(12, 1fr);
    gap: var(--sp-4);
}

/* min-width:auto en un item de rejilla lo estira a su contenido minimo.
   position:relative es para la esquina que redimensiona (.zona-redim). */
.zona-celda {
    min-width: 0;
    position: relative;
}

/* Uno por cada ancho de data/libreria.ANCHOS: una clase sin regla dejaria la
   tarjeta en UNA pista de las doce. */
.zona-ancho-3  { grid-column: span 3; }
.zona-ancho-4  { grid-column: span 4; }
.zona-ancho-6  { grid-column: span 6; }
.zona-ancho-8  { grid-column: span 8; }
.zona-ancho-9  { grid-column: span 9; }
.zona-ancho-12 { grid-column: span 12; }

/* Por debajo de lg el shell se apila y el contenido recupera el viewport
   entero, igual que en .gia-graf-grid: una sola pista y todas las tarjetas a
   ancho completo. Hay que forzar el grid-column porque los span de arriba
   seguirian pidiendo 4, 6 u 8 pistas de una rejilla que ya solo tiene una. */
@media (max-width: 991.98px) {
    .zona-lienzo {
        grid-template-columns: 1fr;
    }

    .zona-celda {
        grid-column: 1 / -1;
    }
}

/* ---- Procedencia: de que seccion es la tarjeta y con que filtros ---- */
/* Va en el CUERPO de la tarjeta y no en la cabecera: .gia-card-acciones es un
   flex que ya tiene que cargar con los controles de edicion. */
.zona-origen {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-2);
    margin-bottom: var(--sp-3);
}

.zona-badge {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-1);
    font-size: var(--fs-xs);
    font-weight: 600;
    color: var(--text-muted);
}

/* Estos chips son ETIQUETAS, no controles: se emiten como span y no como
   button, asi que no deben parecer pulsables. Mismo criterio que ui.stat_card,
   que es un div cuando no filtra y un button cuando si. */
.zona-chip {
    cursor: default;
}

.zona-chip:hover {
    border-color: var(--border);
    color: var(--text);
}

/* Filtro de COLUMNA del listado. Distinto del chip de barra por el mismo
   criterio que el del cliente: no sale de la barra lateral, es un predicado
   sobre una columna del Excel de cabecera. */
/* Se distingue por el icono de tabla, no por un borde discontinuo: en este
   panel el `dashed` ya significa otra cosa (.chip-limpiar, quitar). */
.zona-chip-tabla {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-1);
    border-color: var(--border-strong);
}

.zona-chip-cliente {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-1);
    border-color: var(--border-strong);
}

/* Aviso de que el lienzo mezcla periodos. En tinta neutra y NUNCA con los
   tokens de estado: mirar dos anos a la vez es una decision legitima del
   usuario, no un error, y pintarlo en rojo entrenaria a la gente a ignorar el
   rojo del resto del panel. */
.zona-nota {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    margin-bottom: var(--sp-3);
    color: var(--text-muted);
    font-size: var(--fs-sm);
}

.zona-aviso-detalle {
    margin-top: var(--sp-2);
    color: var(--text-muted);
    font-size: var(--fs-xs);
}

/* ---- Galeria de paneles (/zona) ---- */
.zona-galeria {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 280px), 1fr));
    gap: var(--sp-4);
}

/* Las tarjetas de la galeria se estiran a la altura de la fila para que la
   rejilla no quede dentada cuando unas tienen descripcion y otras no. */
.zona-panel-card {
    display: flex;
    flex-direction: column;
}

.zona-panel-desc {
    color: var(--text-muted);
    margin-bottom: var(--sp-3);
}

.zona-panel-meta {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    margin-bottom: var(--sp-4);
}

.zona-panel-fecha {
    color: var(--text-muted);
    font-size: var(--fs-xs);
}

.zona-panel-borrar {
    color: var(--text-muted) !important;
}

/* El estado vacio es la pantalla que ENSENA la funcion (como se anade un
   grafico), asi que se le da sitio en vez de dejarlo en una linea gris. */
/* Mismo patron que .perm-vacio, que es el estado vacio del panel. */
.zona-vacio {
    text-align: center;
    padding: var(--sp-6) var(--sp-4);
}

.zona-vacio-icono {
    font-size: var(--fs-icono-xl);
    color: var(--text-muted);
    opacity: 0.55;
}

/* Boton de la topbar que abre el dialogo de anclar. A diferencia de los del
   listado, su visibilidad NO la decide una whitelist de data-seccion sino un
   callback (components/modal_zona.mostrar_boton), porque aqui el grano es la
   PAGINA y no la seccion. Por eso aqui no hay display:none de partida: lo pone
   y lo quita la clase d-none de Bootstrap. */
.btn-zona-add {
    display: inline-flex;
    align-items: center;
    font-size: var(--fs-icono-lg);
    padding: var(--sp-1) var(--sp-2);
    text-decoration: none;
}

/* Sin `color` a proposito, al contrario que .btn-filtros-toggle y
   .btn-listado-toggle. Ellos SI lo declaran, pero lo hacen ANTES de
   .btn-link (linea ~917), que con la misma especificidad y tambien
   !important acaba ganando: por eso esos iconos salen en azul de marca y su
   propia regla no se aplica. Como este bloque va DESPUES, declarar el color
   aqui si habria ganado, y el boton salia gris oscuro, el unico distinto de
   los cinco. Callarse es lo que lo iguala a sus vecinos. */

/* Asa del arrastre: el titulo de la tarjeta con su asidero delante
   (assets/zona.js). Se arrastra por el titulo y no por la tarjeta entera
   porque sobre el grafico pelearia con el hover y el zoom de plotly.
   touch-action: none es obligatorio para el dedo: sin el, el navegador decide
   que el gesto es un scroll y no llega ningun pointermove. */
.zona-asa {
    display: flex;
    align-items: center;
    gap: var(--sp-1);
    min-width: 0;
    cursor: grab;
    touch-action: none;
    user-select: none;
}

.zona-asa:active {
    cursor: grabbing;
}

/* El asidero es lo que dice que la tarjeta se puede arrastrar, tambien en
   tableta, donde no hay un cursor que cambie al pasar por encima. Antes solo
   lo descubria quien pasaba el raton por el titulo. */
.zona-asa-icono {
    flex: none;
    margin-left: calc(-1 * var(--sp-1));
    color: var(--text-muted);
    font-size: var(--fs-icono);
}

.zona-asa:hover .zona-asa-icono {
    color: var(--text-strong);
}

/* Esquina que redimensiona. Siempre visible, no solo al pasar por encima: en
   tableta el hover no existe y un gesto que no se ve no se descubre. El icono
   de flechas en diagonal va girado para que apunten a esta esquina. */
.zona-redim {
    position: absolute;
    right: 2px;
    bottom: 2px;
    z-index: 2;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 26px;
    height: 26px;
    border-radius: var(--radius-sm);
    color: var(--text-muted);
    font-size: var(--fs-icono);
    cursor: nwse-resize;
    touch-action: none;
    user-select: none;
}

.zona-redim .bi {
    transform: rotate(90deg);
}

.zona-redim:hover {
    color: var(--text-strong);
    background-color: var(--surface-1);
}

/* Hueco que ensena donde va a caer el grafico que se trae de la paleta. */
.zona-celda-fantasma {
    min-height: var(--graf-h-sm);
    border: 2px dashed var(--gia-primario);
    border-radius: var(--radius);
    background-color: var(--surface-1);
}

/* La tarjeta que se mueve se queda en el lienzo como silueta: es el sitio
   donde va a caer. Lo que sigue al puntero es la etiqueta con su titulo. */
.zona-celda-moviendo {
    opacity: 0.45;
    outline: 2px dashed var(--gia-primario);
    outline-offset: 2px;
    border-radius: var(--radius);
}

.zona-celda-redim {
    outline: 2px solid var(--gia-primario);
    outline-offset: 2px;
    border-radius: var(--radius);
}

.zona-celda-redim .zona-redim {
    color: var(--gia-primario);
}

/* Las doce pistas de la rejilla mientras se redimensiona, para ver a que
   ancho se va a imantar la tarjeta. Cada baldosa del fondo mide una pista mas
   un hueco (el gap de .zona-lienzo) y solo se pinta la pista. */
.zona-lienzo.zona-guias {
    background-image: linear-gradient(to right,
                                      var(--brand-tint) 0,
                                      var(--brand-tint) calc(100% - var(--sp-4)),
                                      transparent calc(100% - var(--sp-4)));
    background-size: calc((100% + var(--sp-4)) / 12) 100%;
}

/* Etiqueta que sigue al puntero durante un arrastre: el titulo de lo que se
   lleva, o el tamano al que se va a quedar. Fija y sin eventos, para que
   elementFromPoint mire lo que hay DEBAJO. */
.zona-arrastre-etiqueta {
    position: fixed;
    left: 0;
    top: 0;
    z-index: 3000;
    pointer-events: none;
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    max-width: 320px;
    padding: var(--sp-1) var(--sp-3);
    background-color: var(--surface-2);
    color: var(--text-strong);
    border: 1px solid var(--gia-primario);
    border-radius: var(--radius-sm);
    box-shadow: var(--shadow-md);
    font-size: var(--fs-sm);
    font-weight: 600;
    white-space: nowrap;
}

.zona-arrastre-etiqueta span {
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Durante el gesto manda su cursor en toda la pagina (el puntero sale de la
   tarjeta enseguida) y no se selecciona texto por el camino. */
body.zona-arrastrando,
body.zona-arrastrando * {
    cursor: grabbing !important;
}

body.zona-redimensionando,
body.zona-redimensionando * {
    cursor: nwse-resize !important;
}

body.zona-arrastrando,
body.zona-redimensionando {
    user-select: none;
}

/* Icono de cada entrada del menu de la tarjeta. */
.zona-menu-icono {
    display: inline-block;
    width: 1.25em;
    margin-right: var(--sp-2);
    color: var(--text-muted);
}

/* ---- Dialogo de ajustes de una tarjeta ---- */
.zona-aj-bloque {
    margin-bottom: var(--sp-4);
}

/* legend de un fieldset: Bootstrap lo pinta como un titular flotante. */
.zona-aj-titulo {
    float: none;
    width: auto;
    margin-bottom: var(--sp-2);
    color: var(--text-strong);
    font-size: var(--fs-sm);
    font-weight: 600;
}

/* ---- Informe por correo de un panel (components/informe_correo.py) ---- */
/* La vista previa va en un iframe para que los estilos en linea del correo y
   los del panel no se mezclen. Trae su propio fondo claro, como el correo. */
.zona-correo-vista {
    display: block;
    width: 100%;
    height: min(640px, 70vh);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    background-color: var(--surface-1);
}

.zona-correo-baja {
    color: var(--danger) !important;
    text-decoration: none;
}

.zona-chip-correo {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-1);
}

/* Botones de eleccion unica que envuelven (dbc.RadioItems con btn-check):
   seis anchos no caben en una fila del dialogo. */
.zona-segmentos {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-1);
}

.zona-segmentos .form-check {
    margin: 0;
    padding-left: 0;
}

/* Botones de edicion en la cabecera de una tarjeta: sus ajustes y su menu.
   Discretos, para que el lienzo se lea como un panel y no como un
   formulario. */
/* Atenuados con COLOR, no con opacity: es como atenua el resto del panel
   (.btn-nav-collapse, .btn-filtros-collapse). Y SIEMPRE visibles, no solo al
   pasar por encima: ningun otro grupo de acciones del panel se esconde asi
   -.gia-card-acciones siempre esta- y en tableta el hover no existe. */
.zona-accion {
    color: var(--text-muted) !important;
    padding: 0 var(--sp-1);
}

.zona-accion:hover:not(:disabled),
.zona-accion:focus-visible {
    color: var(--text-strong) !important;
}

.zona-accion:disabled {
    color: var(--border-strong) !important;
}

.zona-add-pagina {
    color: var(--text-muted);
    font-size: var(--fs-sm);
    margin-bottom: var(--sp-3);
}

/* La lista de graficos de la libreria: una tarjeta por gráfico, con su icono
   de forma y de donde sale. Sin miniatura real a proposito: pintarla seria
   ejecutar las 49 consultas detras de un buscador. */
.zona-add-lista .form-check {
    padding: var(--sp-2) var(--sp-2) var(--sp-2) var(--sp-5);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    margin-bottom: var(--sp-2);
}

.zona-add-lista .form-check:hover {
    border-color: var(--border-strong);
    background-color: var(--surface-1);
}

.zona-add-lista .form-check-label {
    width: 100%;
    cursor: pointer;
}

/* ---- Barra de filtros del panel ----
   Cinta blanca sobre el lienzo, como .topfilters en los informes: ademas de
   ser donde se filtra, es lo que rompe el gris de la pagina. */
.zona-barra {
    background-color: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow-sm);
    padding: var(--sp-3) var(--sp-4);
    margin-bottom: var(--sp-4);
}

.zona-barra-cab {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: var(--sp-2);
}

.zona-barra-tit {
    font-size: var(--fs-xs);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--text-muted);
}

.zona-barra-limpiar {
    color: var(--text-muted) !important;
    text-decoration: none;
}

/* auto-fit y no un numero de columnas fijo: la barra tiene 1 filtro en un
   panel de almacenes y 12 en uno que mezcle tres secciones. */
.zona-barra-huecos {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 200px), 1fr));
    gap: var(--sp-2) var(--sp-3);
}

/* El chip de un filtro que pone la BARRA, no el grafico. Se distingue por el
   embudo: al quitarlo de arriba cambian las cifras de varias tarjetas a la
   vez, y hay que poder saber cual era. */
.zona-chip-barra {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-1);
    border-color: var(--gia-primario);
}

/* La tarjeta cuyo listado no tiene columna de cliente: el filtro del panel se
   descarta ahi sin decir nada, asi que lo dice el chip. En tinta de aviso y no
   de error: la tarjeta no esta rota, solo ensena mas de lo que parece. */
.zona-chip-ojo {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-1);
    color: var(--est-ink-warning);
    border-color: var(--est-bd-warning);
    background-color: var(--est-bg-warning);
}

/* El cliente ocupa mas que un desplegable de marca: es un nombre largo y se
   escribe, no se elige de una lista corta. */
.zona-barra-cliente {
    grid-column: span 2;
}

.zona-barra-nota {
    margin-top: var(--sp-1);
    font-size: var(--fs-xs);
    color: var(--text-muted);
}

/* ---- Paleta de graficos (cajon por la derecha del lienzo) ---- */
.zona-paleta.offcanvas {
    --bs-offcanvas-width: min(380px, 90vw);
}

.zona-paleta-lista .section-header {
    margin-top: var(--sp-4);
    margin-bottom: var(--sp-2);
}

.zona-paleta-lista .section-header:first-child {
    margin-top: 0;
}

/* Cada grafico es un <button> de verdad: se llega con el tabulador y lo lee un
   lector de pantalla. El arrastre se monta encima, no en su lugar. */
.zona-paleta-item {
    display: block;
    width: 100%;
    text-align: left;
    background-color: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    padding: var(--sp-2);
    margin-bottom: var(--sp-2);
}

.zona-paleta-item:hover {
    border-color: var(--border-strong);
    background-color: var(--surface-1);
}

.zona-lib-tarjeta {
    display: flex;
    align-items: flex-start;
    gap: var(--sp-3);
}

.zona-lib-icono {
    font-size: var(--fs-icono-lg);
    color: var(--text-muted);
    margin-top: 2px;
}

.zona-lib-texto {
    display: flex;
    flex-direction: column;
    min-width: 0;
}

.zona-lib-nombre {
    font-weight: 600;
    color: var(--text-strong);
}

.zona-lib-desc {
    font-size: var(--fs-sm);
    color: var(--text-muted);
}

.zona-lib-origen {
    font-size: var(--fs-2xs);
    color: var(--text-muted);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    margin-top: var(--sp-1);
}

/* El contenido de pagina NO lleva tope de ancho: usa toda la pantalla
   (decision de Cesc). Hubo un max-width de 1600px centrado en
   #_pages_content y se quito: en un monitor grande el panel debe aprovechar
   el ancho, aunque eso deje los graficos a fila completa mas apaisados.
   Si algun dia molesta esa proporcion, el arreglo NO es volver a estrechar el
   contenido sino subir un escalon mas la altura en tarjetas muy anchas: una
   @container grafico (min-width: 1100px) mas, junto a las otras dos. */

/* Tarjetas dbc genericas (menu, login, KPI) sobre la nueva superficie */
.card {
    background-color: var(--surface-2);
    border-color: var(--border);
    border-radius: var(--radius);
}

/* ============ Componentes de Bootstrap con los tokens GIA ============
   Bootswatch Flatly pinta los avisos y los badges de color SOLIDO con texto
   blanco, y no derivandolos de --bs-{color}-bg-subtle: verificado moviendo
   --bs-alert-bg en el inspector, el fondo no se movia. Es decir, redefinir
   los tripletes de tema NO basta para estos dos; hacen falta declaraciones
   directas, y ganan porque esta hoja carga despues de la CDN con la misma
   especificidad (comprobado: custom.css va detras de bootstrap.min.css).

   Y hay motivo de accesibilidad, no solo de identidad: el blanco sobre los
   solidos de Flatly da 2,19:1 en warning, 2,41 en success, 2,56 en
   secondary, 3,15 en info y 3,82 en danger, todos por debajo del 4,5:1 que
   pide WCAG para texto normal. De los 39 dbc.Alert del panel, 17 son
   warning, el peor de los cinco. El tratamiento "sutil" (fondo tenido +
   tinta oscura del mismo tono) sube muy por encima del umbral y ademas los
   hace parecer del mismo panel que el resto. */
.alert {
    border-radius: var(--radius);
    border-width: 1px;
    color: var(--text-strong);
    background-color: var(--surface-1);
    border-color: var(--border);
}

.alert-success { color: var(--est-ink-success); background-color: var(--est-bg-success); border-color: var(--est-bd-success); }
.alert-danger  { color: var(--est-ink-danger);  background-color: var(--est-bg-danger);  border-color: var(--est-bd-danger); }
.alert-warning { color: var(--est-ink-warning); background-color: var(--est-bg-warning); border-color: var(--est-bd-warning); }
.alert-info    { color: var(--est-ink-info);    background-color: var(--est-bg-info);    border-color: var(--est-bd-info); }
.alert-secondary { color: var(--est-ink-neutro); background-color: var(--est-bg-neutro); border-color: var(--est-bd-neutro); }

/* El enlace y la X de cierre heredan la tinta del aviso en vez del azul de
   Bootstrap, que sobre el fondo tenido no se distingue. */
.alert .alert-link,
.alert a {
    color: inherit;
    text-decoration: underline;
}

.alert .btn-close {
    filter: none;
}

/* Badges: dbc.Badge emite class="badge bg-{color}", que Flatly pinta solido
   con texto blanco. Mismo tratamiento sutil, con el borde para que no se
   pierdan sobre la superficie de la tarjeta. */
.badge.bg-success { color: var(--est-ink-success); background-color: var(--est-bg-success) !important; border: 1px solid var(--est-bd-success); }
.badge.bg-danger  { color: var(--est-ink-danger);  background-color: var(--est-bg-danger) !important;  border: 1px solid var(--est-bd-danger); }
.badge.bg-warning { color: var(--est-ink-warning); background-color: var(--est-bg-warning) !important; border: 1px solid var(--est-bd-warning); }
.badge.bg-info    { color: var(--est-ink-info);    background-color: var(--est-bg-info) !important;    border: 1px solid var(--est-bd-info); }
.badge.bg-secondary,
.badge.bg-dark    { color: var(--est-ink-neutro);  background-color: var(--est-bg-neutro) !important;  border: 1px solid var(--est-bd-neutro); }

/* Pestanas: estas SI se gobiernan con las variables de ambito de componente
   que declara Bootstrap en la propia regla .nav-tabs (un valor heredado
   desde :root perderia frente a ellas). */
.nav-tabs {
    --bs-nav-tabs-border-color: var(--border);
    --bs-nav-tabs-border-width: 1px;
    --bs-nav-tabs-border-radius: var(--radius-sm);
    --bs-nav-tabs-link-hover-border-color: var(--border-strong);
    --bs-nav-tabs-link-active-color: var(--brand-ink);
    --bs-nav-tabs-link-active-bg: var(--surface-2);
    --bs-nav-tabs-link-active-border-color: var(--border);
    --bs-nav-link-color: var(--text);
    --bs-nav-link-hover-color: var(--brand-ink);
    --bs-nav-link-font-size: var(--fs-md);
    --bs-nav-link-font-weight: 600;
}

/* Tripletes de tema. Estos si funcionan por variable, y cubren de una vez
   .text-bg-*, .border-*-subtle, .link-*, el acordeon y los list-group. Van
   en los DOS bloques de tema: con solo :root, en oscuro nuestro :root (mismo
   peso, fuente posterior) le ganaria al [data-bs-theme=dark] de Bootstrap y
   el modo oscuro se pintaria con colores de claro.
   Sin --bs-{color}-rgb a proposito: pide "r,g,b" literales y estos tonos
   salen de color-mix sobre tokens que el usuario puede cambiar en runtime
   desde el boton de colores, asi que un literal se desincronizaria. Lo que
   depende de -rgb (.bg-* con opacidad) ya lleva regla directa arriba. */
:root,
[data-bs-theme="dark"] {
    --bs-success: var(--success);
    --bs-success-text-emphasis: var(--est-ink-success);
    --bs-success-bg-subtle: var(--est-bg-success);
    --bs-success-border-subtle: var(--est-bd-success);

    --bs-danger: var(--danger);
    --bs-danger-text-emphasis: var(--est-ink-danger);
    --bs-danger-bg-subtle: var(--est-bg-danger);
    --bs-danger-border-subtle: var(--est-bd-danger);

    --bs-warning: var(--warning);
    --bs-warning-text-emphasis: var(--est-ink-warning);
    --bs-warning-bg-subtle: var(--est-bg-warning);
    --bs-warning-border-subtle: var(--est-bd-warning);

    --bs-info: var(--info);
    --bs-info-text-emphasis: var(--est-ink-info);
    --bs-info-bg-subtle: var(--est-bg-info);
    --bs-info-border-subtle: var(--est-bd-info);

    --bs-secondary-color: var(--text-muted);
    --bs-secondary-bg-subtle: var(--est-bg-neutro);
    --bs-secondary-border-subtle: var(--est-bd-neutro);

    --bs-border-color: var(--border);
    --bs-body-color: var(--text);
    --bs-body-bg: var(--surface-1);
    --bs-emphasis-color: var(--text-strong);
    --bs-link-color: var(--brand-ink);
    --bs-link-hover-color: var(--brand-hover);
}

/* ---- AG Grid: variables ancladas a los tokens (adaptan claro/oscuro) ----
   Se fija todo el set sobre ambos temas para no depender de que dash-ag-grid
   cargue las variables de "alpine-dark" (no lo hace de forma fiable). */
.ag-theme-alpine,
.ag-theme-alpine-dark {
    --ag-font-family: var(--font-familia);
    --ag-background-color: var(--surface-2);
    --ag-foreground-color: var(--text);
    --ag-data-color: var(--text);
    --ag-secondary-foreground-color: var(--text-muted);
    --ag-header-background-color: var(--surface-1);
    --ag-header-foreground-color: var(--text-strong);
    --ag-odd-row-background-color: var(--surface-1);
    --ag-row-hover-color: var(--brand-tint);
    --ag-selected-row-background-color: var(--brand-tint);
    --ag-border-color: var(--border);
    --ag-row-border-color: var(--border);
    --ag-input-border-color: var(--border);
    --ag-control-panel-background-color: var(--surface-1);
    --ag-subheader-background-color: var(--surface-1);
    --ag-wrapper-border-radius: var(--radius);
    --ag-header-column-separator-display: none;
}

/* ---- Stat cards (datos rapidos del dashboard y fichas) ---- */
.stat-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
    gap: var(--sp-3);
}

.stat-grid-4 {
    /* 190 y no 170: con 170 la pista se queda en ~145px utiles en tablet, que
       es donde los importes largos se salian. Una fila mas de alto a cambio
       de numeros legibles. */
    grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
}

.stat-grid-3 {
    grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
}

.stat-card {
    background-color: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow-sm);
    padding: var(--sp-4);
    display: flex;
    flex-direction: column;
    gap: 6px;
    min-width: 0;
    /* Contenedor de consulta: lo que hace que el cuerpo de .stat-valor se
       ajuste al ancho de ESTA tarjeta y no al del viewport (los breakpoints
       no ven cuantas columnas ha decidido el auto-fit de la rejilla). */
    container-type: inline-size;
    /* Cinturon: con el clamp de .stat-valor no deberia activarse nunca, pero
       un valor patologico no debe poder pintar sobre la tarjeta vecina. */
    overflow: clip;
}

/* KPI clicable (ui.stat_card con id): filtra el resto de la pagina. Resetea
   el estilo nativo de <button> para que sea indistinguible de la tarjeta
   normal, y marca con el color de marca la que esta aplicada. */
.stat-card-btn {
    font: inherit;
    color: inherit;
    text-align: left;
    cursor: pointer;
    width: 100%;
    transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

.stat-card-btn:hover {
    border-color: var(--brand-ink);
    box-shadow: var(--shadow-md, var(--shadow-sm));
}

.stat-card-btn:focus-visible {
    outline: 2px solid var(--brand-ink);
    outline-offset: 2px;
}

.stat-card-activa {
    border-color: var(--brand-ink);
    background-color: var(--brand-tint);
}

.stat-icono {
    width: 36px;
    height: 36px;
    border-radius: 999px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: var(--fs-icono);
    color: var(--text-on-brand);
    margin-bottom: 2px;
}

/* Colores de icono: identidad decorativa (el dato va en tinta de texto) */
.stat-c1 { background-color: var(--stat-c1); }
.stat-c2 { background-color: var(--stat-c2); }
.stat-c3 { background-color: var(--stat-c3); }
.stat-c4 { background-color: var(--stat-c4); }
.stat-c5 { background-color: var(--stat-c5); }
.stat-c6 { background-color: var(--stat-c6); }
.stat-c7 { background-color: var(--stat-c7); }
.stat-c8 { background-color: var(--stat-c8); }

.stat-titulo {
    font-size: var(--fs-2xs);
    font-weight: 700;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--text-muted);
}

.stat-valor {
    /* Cuerpo fluido con el ancho de la TARJETA (cqi), no del viewport: en las
       rejillas de 4-8 columnas la pista baja a ~150px utiles y un importe de
       9 cifras a 1.45rem mide mas que eso. Sin esto el texto (nowrap, y la
       tarjeta no tenia overflow) se pintaba ENCIMA de la tarjeta vecina, y el
       overflow-x:hidden de .content-col lo recortaba en silencio: no genera
       scroll horizontal, asi que no lo caza ninguna prueba de scroll.
       Nunca ellipsis en un numero: un importe truncado no es un dato
       incompleto, es un dato falso. Se reduce el cuerpo, no la cifra.
       tabular-nums fija el avance del digito y hace el calculo auditable. */
    /* 9cqi y no 7: con 7 el valor no alcanzaba el tope de --fs-xl ni en las
       tarjetas anchas (hacian falta 286px utiles) y se quedaba clavado en el
       suelo, al mismo tamano que el titulo de la tarjeta: la cifra dejaba de
       ser lo mas grande de la tarjeta. Con 9 llega a 20px a partir de 222px
       utiles, y el suelo sigue cubriendo el caso estrecho: en una pista de
       190px (el minimo de .stat-grid-4, medido) quedan 156px utiles, que es
       exactamente lo que mide un importe de 9 cifras a 16px. */
    font-size: clamp(var(--fs-lg), 9cqi, var(--fs-xl));
    font-variant-numeric: tabular-nums;
    font-weight: 700;
    color: var(--text-strong);
    line-height: 1.1;
    white-space: nowrap;
}

.stat-pie {
    display: flex;
    align-items: baseline;
    gap: var(--sp-2);
    flex-wrap: wrap;
}

.stat-delta {
    font-size: var(--fs-sm);
    font-weight: 700;
}

.stat-delta-pos { color: var(--success); }
.stat-delta-neg { color: var(--danger); }

.stat-sub {
    font-size: var(--fs-xs);
    color: var(--text-muted);
}

/* ---- Barras de progreso "ventas por canal" ---- */
.vc-lista {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
    column-gap: var(--sp-6);
    row-gap: var(--sp-3);
}

.vc-cabecera {
    display: flex;
    align-items: baseline;
    gap: var(--sp-2);
    margin-bottom: 4px;
}

.vc-punto {
    width: 10px;
    height: 10px;
    border-radius: 999px;
    flex: 0 0 auto;
    align-self: center;
}

.vc-nombre {
    font-weight: 700;
    font-size: var(--fs-sm);
    color: var(--text-strong);
    text-transform: uppercase;
    letter-spacing: 0.02em;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.vc-valor {
    margin-left: auto;
    font-weight: 700;
    font-size: var(--fs-md);
    color: var(--text-strong);
    white-space: nowrap;
}

.vc-pct {
    font-size: var(--fs-xs);
    color: var(--text-muted);
    min-width: 3.2em;
    text-align: right;
}

.vc-barra {
    height: 8px;
    border-radius: 999px;
    background-color: var(--surface-1);
    border: 1px solid var(--border);
    overflow: hidden;
}

.vc-barra-fill {
    height: 100%;
    border-radius: 999px;
}

/* ---- Accesos del dashboard Principal ---- */
.acceso-link {
    text-decoration: none;
    display: block;
    height: 100%;
}

.acceso-card {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    transition: box-shadow 0.15s ease, transform 0.15s ease, border-color 0.15s ease;
}

.acceso-link:hover .acceso-card {
    border-color: var(--gia-primario);
    box-shadow: var(--shadow-md);
}

.acceso-icono {
    flex: 0 0 auto;
    width: 44px;
    height: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: var(--radius);
    background-color: var(--brand-tint);
    color: var(--brand-ink);
    font-size: var(--fs-icono-lg);
}

.acceso-titulo {
    font-weight: 700;
    color: var(--text-strong);
}

.acceso-desc {
    font-size: var(--fs-sm);
    color: var(--text-muted);
}

/* ---- Explorador de clientes ---- */
.cli-buscar {
    width: 100%;
    padding: 0.5rem 0.75rem;
    margin-bottom: var(--sp-3);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    background-color: var(--surface-1);
    color: var(--text);
    font-size: var(--fs-md);
}

.cli-buscar .dash-input-element {
    flex: 1 1 auto;
    width: 100%;
    padding: 0;
    font-size: inherit;
}

.cli-buscar:focus-within {
    outline: none;
    border-color: var(--gia-primario);
    box-shadow: var(--focus-ring);
}

.cli-lista {
    display: flex;
    flex-direction: column;
    gap: var(--sp-2);
    max-height: calc(100vh - 260px);
    overflow-y: auto;
    padding-right: 2px;
}

.cli-item {
    display: block;
    width: 100%;
    text-align: left;
    background-color: var(--surface-1);
    border: 1px solid var(--border);
    border-left: 3px solid transparent;
    border-radius: var(--radius-sm);
    padding: var(--sp-2) var(--sp-3);
    cursor: pointer;
    color: var(--text);
    text-decoration: none;
}

.cli-item:hover {
    background-color: var(--brand-tint);
    border-left-color: var(--gia-primario);
    color: var(--text);
    text-decoration: none;
}

.cli-item:active {
    transform: translateY(1px);
}

/* Cliente seleccionado (el de la URL ?cif=...) */
.cli-item-activo,
.cli-item-activo:hover {
    background-color: var(--brand-tint);
    border-left-color: var(--gia-primario);
}

.cli-item-top {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-2);
}

.cli-item-nombre {
    font-weight: 700;
    font-size: var(--fs-md);
    color: var(--text-strong);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.cli-item-bot {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-2);
    margin-top: 2px;
}

.cli-item-agente {
    font-size: var(--fs-2xs);
    color: var(--text-muted);
}

.cli-dif-pos {
    font-size: var(--fs-xs);
    font-weight: 600;
    color: var(--success);
}

.cli-dif-neg {
    font-size: var(--fs-xs);
    font-weight: 600;
    color: var(--danger);
}

.cli-badge {
    flex: 0 0 auto;
    font-size: var(--fs-2xs);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    padding: 2px 8px;
    border-radius: 999px;
    background-color: var(--brand-tint);
    color: var(--brand-ink);
}

/* ---- Pagina Canales ---- */
.canal-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
    gap: var(--sp-3);
}

.canal-card {
    background-color: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow-sm);
    padding: var(--sp-4);
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 6px;
    cursor: pointer;
    text-align: left;
    min-width: 0;
    transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

.canal-card:hover {
    border-color: var(--gia-primario);
    box-shadow: var(--shadow-md);
}

.canal-card.canal-activo {
    border-color: var(--gia-primario);
    box-shadow: 0 0 0 1px var(--gia-primario), var(--shadow-sm);
}

.canal-pill {
    font-size: var(--fs-2xs);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    padding: 2px 10px;
    border-radius: 999px;
    color: var(--text-on-brand);
}

.canal-valor {
    font-size: var(--fs-xl);
    font-weight: 700;
    color: var(--text-strong);
    line-height: 1.1;
    white-space: nowrap;
}

.canal-dif {
    font-size: var(--fs-xs);
    font-weight: 600;
    color: var(--text-muted);
}

/* Tabla "Analisis por canal" */
.tabla-canales {
    font-variant-numeric: tabular-nums;
}

.tabla-canales th {
    font-size: var(--fs-2xs);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--text-muted);
    font-weight: 700;
    border-bottom-color: var(--border-strong) !important;
}

.tabla-canales td {
    vertical-align: middle;
}

.tabla-canales .fila-total td {
    font-weight: 700;
    border-top: 2px solid var(--border-strong);
    color: var(--text-strong);
}

/* Tabla "Mes a mes" de Indicadores de presupuestos: doce meses o mas se
   desplazan dentro de su .table-responsive, y la columna del concepto se
   queda fija a la izquierda para no perder de que fila es cada cifra. Fondo
   opaco (el de la tarjeta) para que los numeros no se vean por debajo. */
.tabla-indicadores {
    font-variant-numeric: tabular-nums;
}

.tabla-indicadores .tabla-indicadores-concepto {
    position: sticky;
    left: 0;
    z-index: 1;
    background-color: var(--surface-2);
}

.texto-pos { color: var(--success); font-weight: 600; }
.texto-neg { color: var(--danger); font-weight: 600; }

/* ---- Pie del nav lateral: identidad del usuario ---- */
.nav-footer {
    margin-top: auto;
    padding: var(--sp-3);
    border-top: 1px solid var(--border);
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    min-width: 0;
    -webkit-user-select: none;
    user-select: none;
}

.nav-avatar {
    flex: 0 0 auto;
    width: 36px;
    height: 36px;
    border-radius: 999px;
    background-color: var(--gia-primario);
    color: var(--text-on-brand);
    font-weight: 700;
    font-size: var(--fs-sm);
    display: inline-flex;
    align-items: center;
    justify-content: center;
}

.nav-footer-texto {
    min-width: 0;
}

.nav-footer-nombre {
    font-weight: 700;
    font-size: var(--fs-sm);
    color: var(--text-strong);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.nav-footer-version {
    font-size: var(--fs-2xs);
    color: var(--text-muted);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.nav-footer-online {
    font-size: var(--fs-2xs);
    color: var(--success);
}

.nav-footer-online::before {
    content: "●";
    margin-right: 4px;
}

.app-shell[data-nav-collapsed="1"] .nav-footer-texto {
    display: none;
}

.app-shell[data-nav-collapsed="1"] .nav-footer {
    justify-content: center;
}

/* ---- Metadatos de la topbar (ultima actualizacion) ---- */
.topbar-meta {
    display: inline-flex;
    align-items: baseline;
    gap: var(--sp-2);
    white-space: nowrap;
}

.topbar-meta-label {
    font-size: var(--fs-2xs);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--text-muted);
}

.topbar-meta-valor {
    font-size: var(--fs-sm);
    font-weight: 700;
    color: var(--text-strong);
}

@media (max-width: 991.98px) {
    .topbar-meta {
        display: none;   /* en movil prima el espacio */
    }
}

/* ---- Seccion GPS (pages/gps_mapa.py, pages/gps_recorridos.py) ----
   La fecha de la topbar es la de la ultima carga del almacen de datos: en un
   mapa en vivo no significa nada y se confundiria con la de las posiciones,
   que va en la cabecera de la propia tarjeta (.gps-recepcion). */
body[data-seccion="personal"] .topbar-meta {
    display: none;
}

/* Leyenda de los mapas: un .vc-punto del color de cada traza (lo pone el
   callback, sacado de components/charts.py) y su recuento o su nombre. */
.gps-resumen {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-1) var(--sp-4);
    margin-bottom: var(--sp-3);
    font-size: var(--fs-sm);
    color: var(--text);
}

.gps-resumen-item {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-2);
}

.gps-recepcion {
    font-size: var(--fs-xs);
    color: var(--text-muted);
    white-space: nowrap;
}

/* Estado de la copia diaria a SQL Server (solo administradores), en la zona
   de acciones de la cabecera de /gps/mapa. La linea principal en gris, o con
   la tinta de warning de los avisos si algo va mal; el detalle, debajo. El
   ancho maximo evita que un mensaje de error largo se coma la cabecera. */
.gps-copia {
    font-size: var(--fs-xs);
    color: var(--text-muted);
    max-width: 52ch;
}

.gps-copia-aviso {
    color: var(--est-ink-warning);
    font-weight: 600;
}

.gps-copia-detalle {
    display: block;
}

/* Estado en la lista de moviles. El texto ya lo dice; el color solo lo
   refuerza, con las tintas de estado de los avisos (legibles en los dos
   temas) en el mismo tono que los puntos del mapa. .ag-cell delante para
   ganarle al color de la fila del tema de AG Grid. */
.ag-cell.gps-est-mov {
    color: var(--brand-ink);
    font-weight: 600;
}

.ag-cell.gps-est-parado {
    color: var(--est-ink-warning);
    font-weight: 600;
}

.ag-cell.gps-est-sin {
    color: var(--est-ink-danger);
    font-weight: 600;
}

.ag-cell.gps-est-nada {
    color: var(--text-muted);
}

/* Celdas de la lista que hacen algo al pulsarlas (centrar el mapa, abrir el
   recorrido). */
.ag-cell.gps-accion {
    cursor: pointer;
}

.ag-cell.gps-enlace {
    cursor: pointer;
    color: var(--brand-ink);
}

/* ---- GPS · Jornada y Recorridos (pages/gps_jornada.py, gps_recorridos.py) ----
   Dos modos, que el callback principal pone en gpj-pagina / gpr-pagina en
   la misma respuesta que la figura: *-modo-persona (el detalle de la
   persona del filtro) y *-modo-equipo (el resumen del equipo). Sin modo (al
   cargar) los dos bloques quedan ocultos.
   Sin `container` en la pagina: descolocaria los menus de Radix (custom.css,
   Dash 4); solo en la rejilla del calendario, que no tiene ninguno. */
.gpj-pagina:not(.gpj-modo-persona) .gpj-solo-persona,
.gpj-pagina:not(.gpj-modo-equipo) .gpj-solo-equipo,
.gpr-pagina:not(.gpr-modo-persona) .gpr-solo-persona,
.gpr-pagina:not(.gpr-modo-equipo) .gpr-solo-equipo {
    display: none;
}

/* Columnas de la Jornada: tarjetas apiladas con su alto (no el 100% de
   .gia-card, que con dos en una columna las estira al alto de la fila). */
.gpj-col {
    display: flex;
    flex-direction: column;
    gap: var(--sp-3);
}

.gpj-col > .gia-card {
    height: auto;
}

.gpj-barra {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-2) var(--sp-4);
}

.gpj-dia-nav,
.gpj-moviles,
.gpr-moviles {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-2);
}

/* La ficha del mapa: a que hora, donde y a que velocidad. */
.gpj-ficha {
    min-height: 2.5rem;
    margin-bottom: var(--sp-2);
    padding: var(--sp-2) var(--sp-3);
    border-radius: var(--radius-sm);
    background: var(--surface-2);
    color: var(--text);
    font-size: var(--fs-sm);
    overflow-wrap: anywhere;
}

.gpj-equipo-resumen {
    margin-bottom: var(--sp-2);
    color: var(--text);
    font-size: var(--fs-sm);
}

.ag-row.gpj-equipo-sin {
    color: var(--text-muted);
}

.gpr-calor-pie {
    margin-top: var(--sp-2);
    color: var(--text-muted);
    font-size: var(--fs-xs);
}

.gpj-cal-n {
    display: inline-flex;
    align-items: center;
    gap: 2px;
}

.gpj-estado {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-2);
    font-size: var(--fs-sm);
    color: var(--text);
}

.gpj-dia-titulo {
    font-size: var(--fs-md);
    font-weight: 700;
    color: var(--text-strong);
}

.gpj-mes-txt {
    font-size: var(--fs-sm);
    font-weight: 700;
    color: var(--text-strong);
    min-width: 8.5rem;
    text-align: center;
}

.gpj-cal {
    container: gpjcal / inline-size;
    display: grid;
    grid-template-columns: repeat(7, minmax(0, 1fr));
    gap: 3px;
}

.gpj-cal-sem {
    font-size: var(--fs-2xs);
    font-weight: 700;
    color: var(--text-muted);
    text-align: center;
}

.gpj-cal-dia {
    min-height: 44px;
    min-width: 0;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    padding: 3px 4px;
    font: inherit;
    font-size: var(--fs-xs);
    line-height: 1.1;
    color: var(--text);
    background: transparent;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    cursor: pointer;
}

.gpj-cal-con {
    background: var(--brand-tint);
    border-color: transparent;
}

.gpj-cal-dia:hover:not(:disabled) {
    border-color: var(--brand-ink);
}

.gpj-cal-dia:focus-visible {
    outline: none;
    box-shadow: var(--focus-ring);
}

.gpj-cal-dia:disabled {
    cursor: default;
    color: var(--text-muted);
    border-style: dashed;
}

.gpj-cal-dia[aria-pressed="true"] {
    outline: 2px solid var(--brand-ink);
    outline-offset: 1px;
}

.gpj-cal-hoy .gpj-cal-num {
    text-decoration: underline;
    text-underline-offset: 3px;
}

.gpj-cal-num {
    font-weight: 700;
    align-self: flex-start;
}

.gpj-cal-km {
    align-self: flex-end;
    font-size: var(--fs-2xs);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

@container gpjcal (max-width: 300px) {
    .gpj-cal-km:not(.gpj-cal-n) {
        display: none;
    }

    /* Sin el equipo: el numero de personas cabe; el icono no. */
    .gpj-cal-n i {
        display: none;
    }
}

.gpj-cal-pie {
    margin-top: var(--sp-2);
    font-size: var(--fs-xs);
    color: var(--text-muted);
}

.gpj-kpis {
    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
}

/* Bajo el mapa (xl=8): tres por fila. Seis no caben con el menu desplegado
   entre 1200 y ~1400 px y la cifra se recortaba ("08:12-18:4"): un dato
   falso. */
@media (min-width: 1200px) {
    .gpj-kpis {
        grid-template-columns: repeat(3, minmax(0, 1fr));
    }
}

.gpj-nota-lotes {
    margin-bottom: var(--sp-2);
    font-size: var(--fs-xs);
    color: var(--text-muted);
}

/* Itinerario: cada tramo es un boton (lo senala en el mapa). El activo lo
   marca aria-current desde el clientside: en Safari/iPhone un boton no
   recibe el foco al pulsarlo y :focus no serviria. */
.gpj-itin {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 2px;
    overflow-y: auto;
    max-height: max(320px, min(600px, calc(100dvh - 420px)));
}

.gpj-itin-item {
    display: grid;
    grid-template-columns: 3.4rem 28px minmax(0, 1fr);
    gap: var(--sp-2);
    align-items: start;
    width: 100%;
    padding: var(--sp-2);
    font: inherit;
    color: inherit;
    text-align: left;
    background: transparent;
    border: 0;
    border-left: 3px solid transparent;
    border-radius: var(--radius-sm);
    cursor: pointer;
}

.gpj-itin-item:hover,
.gpj-itin-item[aria-current="true"] {
    background: var(--brand-tint);
    border-left-color: var(--brand-ink);
}

.gpj-itin-item:focus-visible {
    outline: none;
    box-shadow: var(--focus-ring);
}

.gpj-itin-fuera {
    opacity: 0.65;          /* antes de salir / al acabar: no cuentan */
}

.gpj-itin-hora {
    font-size: var(--fs-2xs);
    color: var(--text-muted);
    font-variant-numeric: tabular-nums;
}

.gpj-itin-hora strong {
    display: block;
    font-size: var(--fs-sm);
    color: var(--text-strong);
}

/* El fondo (color del modo) y la tinta (la de mas contraste sobre el) van
   en linea desde charts.colores_modo / charts.tinta_sobre. */
.gpj-itin-icono {
    width: 28px;
    height: 28px;
    border-radius: 999px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: var(--fs-icono);
}

.gpj-itin-texto {
    display: flex;
    flex-direction: column;
    min-width: 0;
}

.gpj-itin-titulo {
    font-size: var(--fs-sm);
    font-weight: 700;
    color: var(--text-strong);
}

.gpj-itin-sub {
    font-size: var(--fs-xs);
    color: var(--text-muted);
}

/* ---- Analisis IA de la Jornada (pintar_ia, data/gps_ia.py) ----
   Veredicto + resumen arriba y dos tablas pequenas: donde paro y la agenda
   del ERP frente al GPS. Los colores, los de estado (badges y celdas). */
.gpj-ia-arriba {
    margin-bottom: var(--sp-2);
}

.gpj-ia-cabecera {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-2);
    margin-bottom: var(--sp-2);
}

.gpj-ia-insignia {
    font-size: var(--fs-xs);
}

.gpj-ia-meta {
    font-size: var(--fs-xs);
    color: var(--text-muted);
}

.gpj-ia-resumen {
    margin-bottom: 0;
    color: var(--text-strong);
}

.gpj-ia-titulo {
    margin: var(--sp-3) 0 var(--sp-2);
    font-size: var(--fs-sm);
    font-weight: 700;
    color: var(--text-strong);
}

.gpj-ia-tabla {
    font-size: var(--fs-sm);
}

.gpj-ia-tabla th,
.gpj-ia-nw {
    white-space: nowrap;
}

/* Direccion, empresa y cliente: con un minimo, para que en pantallas
   estrechas la tabla se desplace dentro de su .table-responsive en vez de
   partir cada direccion en cinco lineas. */
.gpj-ia-ancho {
    min-width: 12rem;
}

.gpj-ia-sub {
    display: block;
    font-size: var(--fs-xs);
    color: var(--text-muted);
}

.gpj-ia-nota {
    min-width: 14rem;
    font-size: var(--fs-xs);
    color: var(--text-muted);
}

.gpj-ia-atenuado {
    color: var(--text-muted);
}

.gpj-ia-fuente {
    color: var(--brand-ink);
}

/* Columna Analisis IA del equipo: el texto ya lo dice; el color refuerza. */
.ag-cell.gpj-ia-ok {
    color: var(--est-ink-success);
    font-weight: 600;
}

.ag-cell.gpj-ia-revisar {
    color: var(--est-ink-warning);
    font-weight: 600;
}

.ag-cell.gpj-ia-mal {
    color: var(--est-ink-danger);
    font-weight: 600;
}

/* ---- Filtro de seleccion de los grids (FiltroSeleccion) ----
   Equivalente interno al set filter de AG Grid Enterprise: buscador,
   "(Seleccionar todo)" y filas clicables enteras con hover. */
.dag-filtro-select {
    padding: 10px;
    width: 300px;
    font-size: var(--fs-md);
}

.dag-filtro-select-busqueda {
    width: 100%;
    box-sizing: border-box;
    margin-bottom: 8px;
    padding: 6px 10px;
    border: 1px solid var(--ag-border-color, var(--border));
    border-radius: 6px;
    background: transparent;
    color: inherit;
}

.dag-filtro-select-busqueda:focus {
    outline: 2px solid var(--gia-primario);
    outline-offset: -1px;
}

.dag-filtro-select-acciones {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 6px;
    color: var(--text-muted);
}

.dag-filtro-select-limpiar {
    border: 1px solid var(--ag-border-color, var(--border));
    border-radius: 6px;
    background: transparent;
    color: inherit;
    padding: 3px 10px;
    cursor: pointer;
}

.dag-filtro-select-limpiar:hover:not(:disabled) {
    background: var(--brand-tint, rgba(37, 99, 235, 0.08));
}

.dag-filtro-select-limpiar:disabled {
    opacity: 0.5;
    cursor: default;
}

/* Flujo en bloque a proposito: con display:flex + max-height, al desbordar
   flex ENCOGE los items (con overflow:hidden colapsan a ~0 y la lista se ve
   aplastada con cientos de valores). */
.dag-filtro-select-lista {
    max-height: 300px;
    overflow-y: auto;
}

.dag-filtro-select-item {
    display: flex;
    align-items: center;
    gap: 8px;
    margin: 0;
    padding: 5px 6px;
    border-radius: 6px;
    cursor: pointer;
    white-space: nowrap;
    overflow: hidden;
}

.dag-filtro-select-item:hover {
    background: var(--brand-tint, rgba(37, 99, 235, 0.08));
}

.dag-filtro-select-item input[type="checkbox"] {
    flex: 0 0 auto;
    width: 15px;
    height: 15px;
    margin: 0;
    accent-color: var(--gia-primario);
    cursor: pointer;
}

.dag-filtro-select-texto {
    overflow: hidden;
    text-overflow: ellipsis;
}

/* "(Seleccionar todo)" anclado sobre la lista, con separador */
.dag-filtro-select-todo {
    font-weight: 600;
    border-bottom: 1px solid var(--ag-border-color, var(--border));
    border-radius: 6px 6px 0 0;
    margin-bottom: 2px;
}

.dag-filtro-select-vacio {
    padding: 8px 6px;
    color: var(--text-muted);
    font-style: italic;
}

/* ---- Selector multiple con checkboxes (components/filtro_multi.py) ----
   Sustituto de dcc.Dropdown(multi=True) en el panel lateral y las barras de
   filtros, con la misma UX que el FiltroSeleccion de los grids (bloque de
   arriba) pero con los tokens del tema (los --ag-* solo existen dentro de
   los grids). Apertura/cierre y busqueda en assets/filtro_multi.js. */
.fm {
    position: relative;
}

.fm-btn {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 6px;
    width: 100%;
    min-height: 34px;
    padding: 4px 8px;
    font-size: var(--fs-sm);
    text-align: left;
    color: var(--text);
    background: var(--surface-2);
    border: 1px solid var(--border-strong);
    border-radius: 4px;
}

.fm-btn:focus-visible {
    outline: none;
    box-shadow: var(--focus-ring);
}

.fm-label {
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
}

.fm-btn.fm-activo .fm-label {
    color: var(--brand-ink);
    font-weight: 600;
}

.fm-caret {
    flex: 0 0 auto;
    color: var(--text-muted);
}

.fm.fm-abierto .fm-caret {
    transform: rotate(180deg);
}

.fm-panel {
    display: none;
    position: absolute;
    top: calc(100% + 2px);
    left: 0;
    z-index: 1015;   /* sobre tarjetas y graficos; bajo topbar (1020) */
    width: max-content;
    min-width: 100%;
    max-width: 340px;
    padding: 8px;
    background: var(--surface-2);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-sm);
    box-shadow: var(--shadow-md);
    font-size: var(--fs-md);
}

.fm.fm-abierto .fm-panel {
    display: block;
}

/* Con .fm-derecha (la pone filtro_multi.js si el panel se sale del viewport)
   el panel se ancla al borde derecho del control. */
.fm-panel.fm-derecha {
    left: auto;
    right: 0;
}

/* En el panel lateral (~250px y overflow-y:auto, que clipa a lo ancho) el
   panel no puede sobresalir: ocupa el ancho del control. */
.sidebar .fm-panel {
    width: 100%;
    max-width: none;
}

/* dcc.Input en Dash 4 pone el className en un contenedor (inline-flex) y el
   <input> real va dentro sin borde: el contenedor lleva el marco y el input
   se estira; el foco se detecta con :focus-within. */
.fm-buscar {
    width: 100%;
    box-sizing: border-box;
    margin-bottom: 8px;
    padding: 6px 10px;
    border: 1px solid var(--border-strong);
    border-radius: 6px;
    background: transparent;
    color: inherit;
}

.fm-buscar .dash-input-element {
    flex: 1 1 auto;
    width: 100%;
    padding: 0;
}

.fm-buscar:focus-within {
    outline: 2px solid var(--gia-primario);
    outline-offset: -1px;
}

/* Flujo en bloque a proposito, como .dag-filtro-select-lista: con flex +
   max-height los items encogen al desbordar. */
.fm-lista {
    max-height: 300px;
    overflow-y: auto;
}

/* .fm-item viste el boton "(Todos)"; las filas del dbc.Checklist son
   div.form-check con el input y el label dentro. La fila entera es flex
   (se anulan el padding-left y el margen negativo de Bootstrap). */
.fm-item,
.fm-lista .form-check {
    display: flex;
    align-items: center;
    gap: 8px;
    margin: 0;
    padding: 6px 8px;
    border-radius: 6px;
    cursor: pointer;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.fm-item:hover,
.fm-lista .form-check:hover {
    background: var(--brand-tint);
}

.fm-lista .form-check-input {
    flex: 0 0 auto;
    width: 15px;
    height: 15px;
    margin: 0;
    cursor: pointer;
}

/* Bootstrap dibuja el checkbox (appearance:none): el marcado se tiñe con
   los tokens, no con accent-color. */
.fm-lista .form-check-input:checked {
    background-color: var(--gia-primario);
    border-color: var(--gia-primario);
}

/* El label ocupa el resto de la fila (toda la fila clicable) y corta con
   elipsis los valores largos. */
.fm-lista .form-check-label {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    cursor: pointer;
}

/* "(Todos)" anclado sobre la lista: un boton con checkbox dibujado en CSS
   (uno real se alternaria solo al clicar y su estado lo gobierna el value
   del Checklist, no el click). filtro_multi.js le pone .fm-marcado cuando
   la seleccion esta vacia (= sin filtro). */
.fm-todos {
    width: 100%;
    font: inherit;
    font-weight: 600;
    color: inherit;
    background: transparent;
    border: 0;
    border-bottom: 1px solid var(--border);
    border-radius: 6px 6px 0 0;
    margin-bottom: 2px;
}

.fm-todos-check {
    flex: 0 0 auto;
    position: relative;
    width: 15px;
    height: 15px;
    border: 1px solid var(--border-strong);
    border-radius: 3px;
    background: transparent;
}

.fm-todos.fm-marcado .fm-todos-check {
    background: var(--gia-primario);
    border-color: var(--gia-primario);
}

.fm-todos.fm-marcado .fm-todos-check::after {
    content: "";
    position: absolute;
    left: 4px;
    top: 1px;
    width: 5px;
    height: 9px;
    border: solid var(--text-on-brand);
    border-width: 0 2px 2px 0;
    transform: rotate(45deg);
}

.fm-vacio {
    padding: 8px 6px;
    color: var(--text-muted);
    font-style: italic;
}

.fm-oculto {
    display: none !important;
}

/* ==================== Pagina de permisos por perfil ====================
   Todo con los tokens del panel (--sp-*, --surface-*, --border, --radius-*):
   ninguna medida ni color a pelo. */

/* Es un formulario, no un dashboard: a ancho completo el desplegable de
   perfil medía media pantalla. */
.perm-panel {
    max-width: 1040px;
}

.perm-label {
    font-weight: 600;
    color: var(--text-strong);
    margin-bottom: var(--sp-1);
}

.perm-flash:not(:empty) {
    margin-top: var(--sp-3);
}

/* ---- estado vacio (sin perfil elegido) ---- */
.perm-vacio {
    text-align: center;
    padding: var(--sp-6) var(--sp-4);
}

.perm-vacio-icono {
    font-size: var(--fs-icono-xl);
    color: var(--text-muted);
    opacity: 0.55;
}

.perm-vacio-texto {
    margin: var(--sp-3) 0 var(--sp-1);
    font-weight: 600;
    color: var(--text-strong);
}

.perm-vacio-ayuda {
    margin: 0;
    font-size: var(--fs-md);
    color: var(--text-muted);
}

/* ---- los dos flags del perfil ---- */
.perm-flags {
    display: flex;
    flex-direction: column;
    gap: var(--sp-2);
}

/* El aviso de que son columnas del ERP: aqui, junto a los dos controles que
   afecta, en vez de como banda naranja a toda pagina. */
.perm-nota-erp {
    margin: var(--sp-3) 0 0;
    padding-top: var(--sp-3);
    border-top: 1px solid var(--border);
    font-size: var(--fs-sm);
    color: var(--text-muted);
}

.perm-nota-erp code {
    color: var(--text);
    background-color: var(--surface-1);
    padding: 1px 4px;
    border-radius: var(--radius-sm);
}

/* ---- acordeon de secciones ---- */
.perm-cuerpo-acordeon {
    padding: 0;
}

.perm-total {
    font-size: var(--fs-sm);
    color: var(--text-muted);
    white-space: nowrap;
}

.perm-seccion-titulo {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    width: 100%;
}

.perm-seccion-icono {
    color: var(--text-muted);
    width: 1.1rem;
    text-align: center;
}

.perm-seccion-nombre {
    font-weight: 600;
    color: var(--text-strong);
}

/* El contador es lo que hace legible el acordeon cerrado: sin el, la cabecera
   solo decia cuantas paginas TIENE la seccion, no cuantas ve el perfil. */
.perm-conteo {
    margin-left: auto;
    margin-right: var(--sp-2);
    font-size: var(--fs-xs);
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    padding: 2px var(--sp-2);
    border-radius: 999px;
    border: 1px solid var(--border-strong);
    color: var(--text-muted);
    background-color: var(--surface-1);
}

.perm-conteo-parcial {
    color: var(--brand-ink);
    border-color: var(--brand-ink);
    background-color: var(--brand-tint);
}

.perm-conteo-todo {
    color: var(--text-on-brand);
    border-color: var(--brand-ink);
    background-color: var(--brand-ink);
}

.perm-toda-seccion {
    font-weight: 600;
    padding-bottom: var(--sp-2);
    margin-bottom: var(--sp-2);
    border-bottom: 1px solid var(--border);
}

/* Dos columnas cuando caben: 7 paginas en vertical hacian scrollear. */
.perm-paginas {
    columns: 2;
    column-gap: var(--sp-5);
}

@media (max-width: 575.98px) {
    .perm-paginas { columns: 1; }
}

/* Rotulo del grupo delante del nombre de la pagina ("UBICACIÓN GPS Mapa en
   vivo"), como el del menu. */
.perm-encabezado {
    font-size: var(--fs-2xs);
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--text-muted);
    margin-right: var(--sp-1);
}

/* ---- barra de acciones ---- */
/* Pegada abajo: con las 12 secciones desplegadas, Guardar quedaba fuera de
   pantalla. */
.perm-acciones {
    position: sticky;
    bottom: 0;
    z-index: 5;
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    margin-top: var(--sp-4);
    padding: var(--sp-3) 0;
    background-color: var(--surface-1);
    border-top: 1px solid var(--border);
}

.perm-sin-guardar {
    margin-right: auto;
    font-size: var(--fs-md);
    font-weight: 600;
    color: var(--danger);
}

/* ---- pestana de acceso: los perfiles de cada usuario ---- */
/* Pastilla y no el numero a pelo: tiene que leerse como algo que se despliega
   al pasar por encima. Mismo dibujo que .perm-conteo, la otra cuenta de la
   pagina, y la marca al pasar por encima como .chip. */
.acc-perfiles {
    min-width: 2rem;
    padding: 1px var(--sp-2);
    font-size: var(--fs-sm);
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    line-height: 1.4;
    color: var(--text);
    background-color: var(--surface-1);
    border: 1px solid var(--border-strong);
    border-radius: 999px;
}

.acc-perfiles:hover,
.acc-perfiles:focus-visible {
    color: var(--brand-ink);
    border-color: var(--brand-ink);
    background-color: var(--brand-tint);
}

.acc-pop-lista {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: var(--sp-1);
}

.acc-pop-fila {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--sp-3);
}

.acc-pop-nombre {
    color: var(--text-strong);
}

.acc-pop-detalle {
    flex: none;
    font-size: var(--fs-xs);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
    color: var(--text-muted);
}

/* FullAccess manda sobre las paginas de todos sus otros perfiles. */
.acc-pop-todo {
    font-weight: 600;
    color: var(--brand-ink);
}

.acc-pop-nota {
    margin: var(--sp-2) 0 0;
    padding-top: var(--sp-2);
    border-top: 1px solid var(--border);
    font-size: var(--fs-xs);
    color: var(--text-muted);
}

/* ==========================================================================
   Asistente del panel (components/chat_ia.py)
   --------------------------------------------------------------------------
   Boton flotante en la esquina inferior derecha y panel de chat NO modal: la gracia es
   ver cambiar el panel de detras mientras el asistente lo filtra, asi que
   nada de dbc.Offcanvas (trae fondo oscuro y atrapa el foco).

   z-index 1012: por encima de la cinta de chips (1010) y del contenido, y por
   DEBAJO de .fm-panel (1015) y de la topbar (1020), de modo que un selector de
   filtro abierto se sigue viendo sobre el chat.

   Cuelga de #app-shell y no de .content-col: position:fixed sobrevive, pero
   dentro de la columna quedaria recortado por el overflow de .app-body.
   ========================================================================== */

/* --------------------------------------- mando a distancia (MCP) */

/* El chip es el control del mando y se ve SIEMPRE que la persona lo tenga
   concedido: apagado invita a escuchar, encendido lo dice y trae el id de la
   ventana. Los dos estados los pinta data-mando en el shell, que pone el
   clientside de components/mando_ia.py, igual que el chat hace con data-ia.
   Va encima del boton del chat para no pisarlo cuando los dos estan puestos. */
.mando-chip { display: none; }

#app-shell[data-mando] .mando-chip {
    position: fixed;
    right: var(--sp-4);
    bottom: calc(var(--sp-4) + 64px);
    z-index: 1012;
    display: inline-flex;
    align-items: center;
    gap: var(--sp-1);
    padding: var(--sp-1) var(--sp-2);
    border: 1px solid var(--gia-acento);
    border-radius: 999px;
    background: var(--surface-1);
    color: var(--gia-acento);
    font-size: var(--fs-sm);
    line-height: 1;
    cursor: pointer;
    box-shadow: var(--shadow-md);
}

/* Apagado: discreto, que no compita con el contenido. */
#app-shell[data-mando="no"] .mando-chip {
    border-color: var(--border-strong);
    color: var(--text-muted);
    box-shadow: var(--shadow-sm);
}

#app-shell[data-mando] .mando-chip:hover { background: var(--surface-2); }
.mando-chip:focus-visible { box-shadow: var(--focus-ring); outline: none; }

.ia-fab {
    position: fixed;
    bottom: var(--sp-4);
    right: var(--sp-4);
    z-index: 1012;
    width: 52px;
    height: 52px;
    padding: 0;
    border: none;
    border-radius: 999px;
    background: var(--gia-primario);
    color: var(--text-on-brand);
    box-shadow: var(--shadow-md);
    font-size: var(--fs-icono-lg);
    line-height: 1;
    cursor: pointer;
}

.ia-fab:hover { background: var(--brand-hover); }
.ia-fab:focus-visible { box-shadow: var(--focus-ring); outline: none; }

/* Esquina inferior DERECHA de la ventana, que es donde los widgets de este
   tipo se ponen y donde la gente los busca. Flota sobre el contenido, asi que
   la columna gana hueco abajo para que la ultima tarjeta no quede debajo: en
   escritorio el scroll vive DENTRO de .content-col, o sea que lo que tape no
   se puede descubrir bajando. */
.content-col { padding-bottom: calc(var(--sp-5) + 56px); }

@media (max-width: 991.98px) {
    .content-col { padding-bottom: calc(var(--sp-3) + 56px); }
}

.app-shell[data-ia="abierto"] .ia-fab { display: none; }

/* -------------------------------------------------------------- el panel */

.ia-panel {
    position: fixed;
    z-index: 1012;
    display: none;
    flex-direction: column;
    right: var(--sp-4);
    bottom: var(--sp-4);
    width: min(400px, calc(100vw - 2 * var(--sp-4)));
    height: min(600px, calc(100dvh - var(--topbar-h) - var(--sp-5)));
    background: var(--surface-0);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-md);
    overflow: hidden;
}

.app-shell[data-ia="abierto"] .ia-panel { display: flex; }

/* En movil el nav ya es un cajon y no hay sitio para una ventanita. */
@media (max-width: 991.98px) {
    .app-shell[data-ia="abierto"] .ia-panel {
        inset: var(--topbar-h) 0 0 0;
        width: auto;
        height: auto;
        border-radius: 0;
        border-left: none;
        border-right: none;
    }
}

.ia-cabecera {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-2);
    padding: var(--sp-2) var(--sp-3);
    background: var(--surface-1);
    border-bottom: 1px solid var(--border);
}

.ia-titulo {
    font-size: var(--fs-md);
    font-weight: 600;
    color: var(--text-strong);
}

/* Los dos botones de icono de la cabecera: conversación nueva y cerrar. Van
   agrupados para que el space-between de .ia-cabecera los deje juntos en su
   esquina en vez de repartir los tres hijos por el ancho. */
.ia-cab-acciones {
    display: flex;
    align-items: center;
    gap: var(--sp-1);
}

.ia-cab-boton {
    border: none;
    background: none;
    color: var(--text-muted);
    font-size: var(--fs-icono);
    line-height: 1;
    padding: var(--sp-1);
    border-radius: var(--radius-sm);
    cursor: pointer;
}

.ia-cab-boton:hover { color: var(--text-strong); background: var(--surface-2); }
.ia-cab-boton:focus-visible { box-shadow: var(--focus-ring); outline: none; }

/* ------------------------------------------------------------- mensajes */

.ia-mensajes {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    padding: var(--sp-3);
    display: flex;
    flex-direction: column;
    gap: var(--sp-2);
    font-size: var(--fs-sm);
    color: var(--text);
}

.ia-vacio {
    color: var(--text-muted);
    font-size: var(--fs-sm);
    line-height: 1.5;
}

.ia-msg {
    max-width: 92%;
    padding: var(--sp-2) var(--sp-3);
    border-radius: var(--radius);
    line-height: 1.45;
}

.ia-msg-yo {
    align-self: flex-end;
    background: var(--brand-tint);
    color: var(--text-strong);
}

.ia-msg-ia {
    align-self: flex-start;
    background: var(--surface-1);
    max-width: 100%;
}

/* dcc.Markdown mete parrafos con margen propio. */
.ia-texto p { margin-bottom: var(--sp-1); }
.ia-texto p:last-child { margin-bottom: 0; }

.ia-error {
    color: var(--danger);
    font-size: var(--fs-sm);
}

/* ------------------------------------------------------ tarjeta de accion */

.ia-tarjeta {
    margin-top: var(--sp-2);
    padding: var(--sp-2);
    background: var(--surface-0);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
}

.ia-tarjeta-titulo {
    font-size: var(--fs-xs);
    font-weight: 600;
    color: var(--text-strong);
    margin-bottom: var(--sp-1);
}

.ia-cambios {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: 2px var(--sp-2);
    margin: 0;
    font-size: var(--fs-2xs);
}

.ia-cambios dt { color: var(--text-muted); font-weight: 400; }
.ia-cambios dd { margin: 0; color: var(--text); }
.ia-cambios dd i { margin: 0 var(--sp-1); color: var(--text-muted); }
.ia-antes { color: var(--text-muted); text-decoration: line-through; }
.ia-despues { color: var(--text-strong); font-weight: 600; }

.ia-nota { font-size: var(--fs-2xs); color: var(--text-muted); }

.ia-acciones {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-2);
    margin-top: var(--sp-2);
}

.ia-boton {
    font-size: var(--fs-2xs);
    padding: 0;
    border: none;
    background: none;
    color: var(--brand-ink);
    cursor: pointer;
}

.ia-boton:hover { text-decoration: underline; }
.ia-boton:focus-visible { box-shadow: var(--focus-ring); outline: none; }

/* ------------------------------------------------------------ el redactor */

.ia-pie {
    display: flex;
    align-items: flex-end;
    gap: var(--sp-2);
    padding: var(--sp-2);
    border-top: 1px solid var(--border);
    background: var(--surface-0);
}

.ia-entrada {
    flex: 1 1 auto;
    resize: none;
    max-height: 140px;
    padding: var(--sp-2);
    font-size: var(--fs-sm);
    font-family: inherit;
    color: var(--text);
    background: var(--surface-0);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-sm);
}

.ia-entrada:focus-visible { box-shadow: var(--focus-ring); outline: none; }

.ia-enviar {
    flex: 0 0 auto;
    width: 36px;
    height: 36px;
    border: none;
    border-radius: var(--radius-sm);
    background: var(--gia-primario);
    color: var(--text-on-brand);
    font-size: var(--fs-icono);
    line-height: 1;
    cursor: pointer;
}

.ia-enviar:hover:not(:disabled) { background: var(--brand-hover); }
.ia-enviar:disabled { opacity: .55; cursor: default; }
.ia-enviar:focus-visible { box-shadow: var(--focus-ring); outline: none; }

/* Mientras piensa: el boton ya sale deshabilitado por `running`, esto solo
   subraya la cabecera con una barra que late. */
.ia-panel.ia-pensando .ia-cabecera { border-bottom-color: var(--gia-acento); }

.ia-panel.ia-pensando .ia-cabecera::after {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    bottom: -1px;
    height: 2px;
    background: linear-gradient(90deg, transparent, var(--gia-acento), transparent);
}

@media (prefers-reduced-motion: no-preference) {
    .ia-panel.ia-pensando .ia-cabecera::after {
        animation: ia-progreso 1.2s ease-in-out infinite;
    }
}

@keyframes ia-progreso {
    0%   { opacity: .25; }
    50%  { opacity: 1; }
    100% { opacity: .25; }
}

/* La lista de mensajes, dentro del contenedor que scrollea (#ia-scroll): la
   burbuja de "Pensando" es hermana suya y no hija, porque la lista la repinta
   un callback entera. */
.ia-lista {
    display: flex;
    flex-direction: column;
    gap: var(--sp-2);
}

/* `hidden` de HTML sobre un hijo de flex: sin esto cualquier display heredado
   ganaria al atributo y la burbuja se quedaria puesta. */
.ia-msg[hidden] { display: none; }

.ia-pensando-burbuja {
    align-self: flex-start;
    margin-top: var(--sp-2);
    color: var(--text-muted);
    font-size: var(--fs-sm);
}

.ia-puntos::after {
    content: "...";
    display: inline-block;
    width: 1.2em;
    text-align: left;
    letter-spacing: .1em;
}

/* Los puntos se comen a si mismos para dar sensacion de actividad. Sin
   animacion (o si el sistema la desaconseja) se quedan los tres fijos, que
   siguen diciendo lo mismo. */
@media (prefers-reduced-motion: no-preference) {
    .ia-puntos::after { animation: ia-puntos 1.4s steps(4, end) infinite; }
}

@keyframes ia-puntos {
    0%   { content: ""; }
    25%  { content: "."; }
    50%  { content: ".."; }
    75%  { content: "..."; }
    100% { content: ""; }
}

/* ============ Pagina de login ============
   La unica pantalla que se ve sin sesion, y por tanto la portada del panel:
   a la izquierda la marca (los dos azules corporativos en degradado, el
   mismo par --gia-primario/--gia-acento que el usuario puede cambiar desde
   el boton de colores, asi que la portada sigue al tema sola) y a la derecha
   el acceso, sin tarjeta. Todo con los tokens del sistema; el logo es el mismo
   PNG del navbar, y en el panel de marca va pasado a blanco por filtro para
   que aguante sobre el azul. */
.login-shell {
    display: grid;
    grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr);
    min-height: 100vh;
}

.login-marca {
    position: relative;
    isolation: isolate;
    overflow: hidden;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    text-align: center;
    gap: var(--sp-6);
    padding: clamp(2rem, 4.5vw, 4.5rem);
    color: var(--text-on-brand);
    background: linear-gradient(145deg,
        color-mix(in srgb, var(--gia-primario) 78%, black) 0%,
        var(--gia-primario) 50%,
        color-mix(in srgb, var(--gia-acento) 90%, black) 100%);
}

/* Dos halos suaves + una retícula de puntos: da profundidad sin meter una
   imagen de fondo (0 bytes de descarga) y no compite con el texto. */
.login-marca::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: -1;
    background:
        radial-gradient(58% 52% at 80% 8%, rgba(255, 255, 255, .20), transparent 68%),
        radial-gradient(48% 46% at 6% 96%, rgba(255, 255, 255, .13), transparent 70%);
}

.login-marca::after {
    content: "";
    position: absolute;
    inset: 0;
    z-index: -1;
    background-image: radial-gradient(rgba(255, 255, 255, .16) 1px, transparent 1px);
    background-size: 22px 22px;
    opacity: .5;
    -webkit-mask-image: linear-gradient(120deg, black, transparent 62%);
    mask-image: linear-gradient(120deg, black, transparent 62%);
}

.login-logo {
    height: 44px;
    width: auto;
    /* El PNG es azul + gris; sobre el degradado solo funciona en blanco. */
    filter: brightness(0) invert(1);
}

.login-marca-cuerpo {
    max-width: 34rem;
}

.login-titular {
    margin: var(--sp-5) auto var(--sp-3);
    max-width: 17ch;
    font-size: calc(var(--fs-2xl) * 1.5);
    font-weight: 700;
    line-height: 1.12;
    letter-spacing: -0.015em;
}

.login-claim {
    margin: 0 auto;
    max-width: 42ch;
    font-size: var(--fs-lg);
    line-height: 1.5;
    color: rgba(255, 255, 255, .88);
}

.login-puntos {
    list-style: none;
    margin: var(--sp-6) auto 0;
    padding: 0;
    display: grid;
    gap: var(--sp-3);
    width: max-content;
    max-width: 100%;
    text-align: left;
}

.login-puntos li {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    font-size: var(--fs-md);
    color: rgba(255, 255, 255, .92);
}

.login-puntos i {
    flex: 0 0 auto;
    display: grid;
    place-items: center;
    width: 34px;
    height: 34px;
    border-radius: var(--radius-sm);
    background: rgba(255, 255, 255, .15);
    font-size: var(--fs-icono);
}

/* Va en el flujo del bloque centrado, no anclado al fondo del panel. */
.login-marca-pie {
    margin: 0;
    font-size: var(--fs-xs);
    letter-spacing: .02em;
    color: rgba(255, 255, 255, .72);
}

.login-acceso {
    display: grid;
    place-items: center;
    padding: clamp(1.5rem, 4vw, 3.5rem);
    background-color: var(--surface-1);
}

/* Sin tarjeta: el logo, el titulo y el boton van directamente sobre el fondo
   (decision de Cesc). Un cuadro con borde y sombra dentro de una mitad que ya
   esta delimitada por el panel de marca era una caja dentro de otra. */
.login-acceso-cuerpo {
    width: 100%;
    max-width: 380px;
    text-align: center;
}

.login-logo-acceso {
    height: 64px;
    width: auto;
    margin-bottom: var(--sp-5);
}

/* En oscuro el PNG (azul + gris) se apaga sobre la superficie: mismo chip
   claro que lleva el logo del navbar en .nav-logo. */
[data-bs-theme="dark"] .login-logo-acceso {
    background-color: #f2f2f2;
    border-radius: var(--radius-sm);
    padding: var(--sp-2) var(--sp-3);
}

.login-titulo {
    margin: 0 0 var(--sp-2);
    font-size: var(--fs-xl);
    font-weight: 700;
    color: var(--text-strong);
}

.login-texto {
    margin: 0 0 var(--sp-5);
    font-size: var(--fs-md);
    line-height: 1.5;
    color: var(--text-muted);
}

.login-boton.btn {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--sp-3);
    width: 100%;
    padding: var(--sp-3) var(--sp-4);
    font-size: var(--fs-md);
    font-weight: 600;
    color: var(--text-on-brand);
    background-color: var(--gia-primario);
    border-color: var(--gia-primario);
    border-radius: var(--radius);
    box-shadow: var(--shadow-sm);
}

.login-boton.btn:hover,
.login-boton.btn:active,
.login-boton.btn:focus-visible {
    color: var(--text-on-brand);
    background-color: var(--brand-hover);
    border-color: var(--brand-hover);
}

.login-boton.btn:focus-visible {
    outline: none;
    box-shadow: var(--focus-ring);
}

/* El logo de Microsoft va sobre blanco SIEMPRE (sus cuatro colores sobre el
   azul de marca pierden el rojo), asi que viaja en su propia pastilla. */
.login-ms-chip {
    flex: 0 0 auto;
    display: grid;
    place-items: center;
    width: 28px;
    height: 28px;
    border-radius: var(--radius-sm);
    background-color: white;
}

.login-ms-logo {
    width: 18px;
    height: 18px;
    display: block;
}

.login-error {
    display: flex;
    align-items: flex-start;
    text-align: left;
    gap: var(--sp-2);
    margin-top: var(--sp-4);
    padding: var(--sp-3);
    font-size: var(--fs-sm);
    line-height: 1.45;
    color: var(--est-ink-danger);
    background-color: var(--est-bg-danger);
    border: 1px solid var(--est-bd-danger);
    border-radius: var(--radius-sm);
}

.login-acceso-pie {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--sp-1);
    margin-top: var(--sp-5);
    font-size: var(--fs-xs);
    color: var(--text-muted);
}

.login-version {
    font-variant-numeric: tabular-nums;
    opacity: .8;
}

/* Movil y tableta en vertical: el panel de marca se queda en cabecera (logo,
   titular y claim) y la caja pasa debajo. Los argumentos y el copyright se
   retiran antes que encoger el bloque de acceso, que es a lo que se viene. */
@media (max-width: 991.98px) {
    .login-shell {
        grid-template-columns: 1fr;
        min-height: 100dvh;
    }

    .login-marca {
        gap: var(--sp-4);
        padding: var(--sp-5) var(--sp-4);
    }

    .login-logo { height: 32px; }

    .login-titular {
        margin: var(--sp-4) 0 var(--sp-2);
        max-width: none;
        font-size: calc(var(--fs-2xl) * 1.05);
    }

    .login-claim { font-size: var(--fs-md); }

    .login-puntos,
    .login-marca-pie { display: none; }

    .login-acceso {
        padding: var(--sp-6) var(--sp-4);
        align-items: start;
    }

    .login-logo-acceso {
        height: 52px;
        margin-bottom: var(--sp-4);
    }
}

/* ---- Pagina de versiones (pages/version.py, components/novedades.py) ----
   El "Versión: X.Y.Z" del pie de la navegacion es un enlace a /version, con
   los componentes del panel y las notas de cada version. */
.nav-version-btn {
    display: block;
    max-width: 100%;
    padding: 0;
    border: 0;
    background: none;
    font: inherit;
    font-size: var(--fs-2xs);
    color: var(--text-muted);
    text-align: left;
    text-decoration: none;
    cursor: pointer;
}

.nav-version-btn:hover {
    color: var(--brand-ink);
    text-decoration: underline;
}

.nav-drawer-pie {
    margin-top: var(--sp-4);
    padding-top: var(--sp-3);
    border-top: 1px solid var(--border);
}

.nav-version-btn:focus-visible {
    outline: 2px solid var(--gia-primario);
    outline-offset: 2px;
    border-radius: 2px;
}

.novedades-intro {
    margin-bottom: var(--sp-2);
    color: var(--text);
    font-size: var(--fs-sm);
}

.novedades {
    margin: 0;
    padding: 0;
    list-style: none;
}

.novedades-item {
    padding: var(--sp-3) 0;
    border-top: 1px solid var(--border);
}

.novedades-sin-parches .novedades-item-parche {
    display: none;
}

.novedades-cab {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--sp-1) var(--sp-2);
}

.novedades-version {
    font-weight: 700;
    color: var(--text-strong);
}

.novedades-tipo,
.novedades-actual {
    padding: 0 var(--sp-2);
    border-radius: 999px;
    font-size: var(--fs-2xs);
    font-weight: 700;
    border: 1px solid var(--border-strong);
    color: var(--text);
}

.novedades-tipo-mayor {
    border-color: var(--gia-primario);
    background: var(--gia-primario);
    color: var(--text-on-brand);
}

.novedades-tipo-menor {
    border-color: var(--brand-ink);
    color: var(--brand-ink);
}

.novedades-actual {
    border-color: var(--success);
    color: var(--success);
}

.novedades-fecha {
    font-size: var(--fs-xs);
    color: var(--text-muted);
}

.novedades-titulo {
    margin-top: var(--sp-1);
    font-weight: 600;
    color: var(--text-strong);
}

.novedades-lista {
    margin: var(--sp-1) 0 0;
    padding-left: 1.2rem;
    color: var(--text);
    font-size: var(--fs-sm);
}

/* Tabla de componentes: la version en una columna estrecha a la derecha,
   con numeros alineados, y la fila del propio panel destacada sobre las
   librerias. */
.versiones-tabla .versiones-num {
    width: 1%;
    text-align: right;
    font-variant-numeric: tabular-nums;
    font-weight: 600;
    white-space: nowrap;
}

.versiones-nombre {
    font-weight: 600;
}

.versiones-para {
    font-weight: 400;
    color: var(--text-muted);
    font-size: var(--fs-sm);
}

.versiones-app .versiones-nombre,
.versiones-app .versiones-num {
    color: var(--brand-ink);
}

/* ---- Informe Contenedores (components/ficha_contenedor.py) ----
   Dias de retraso en celdas y textos: en plazo, leve (1-15 dias) y grave
   (mas de 15), con los tokens de estado. Las fechas previstas (todavia no
   reales), en cursiva y apagadas. */
.cn-retraso-plazo {
    color: var(--est-ink-success);
}

.cn-retraso-leve {
    color: var(--est-ink-warning);
    font-weight: 600;
}

.cn-retraso-grave {
    color: var(--est-ink-danger);
    font-weight: 700;
}

.cn-retraso-nd {
    color: var(--text-muted);
}

.ag-cell.cn-retraso-leve {
    background: var(--est-bg-warning);
}

.ag-cell.cn-retraso-grave {
    background: var(--est-bg-danger);
}

.cn-prevista {
    font-style: italic;
    color: var(--text-muted);
}

/* Cajon de la ficha (por la derecha y sin fondo, como .zona-paleta). */
.ficha-contenedor.offcanvas {
    --bs-offcanvas-width: min(880px, 100vw);
}

.cn-ficha-sub {
    margin: var(--sp-3) 0 var(--sp-2);
    font-size: var(--fs-sm);
    font-weight: 700;
    color: var(--text-strong);
}

/* Linea de tiempo plan frente a real: un paso por hito. */
.cn-hitos {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
    gap: var(--sp-2);
    margin: 0 0 var(--sp-2);
    padding: 0;
    list-style: none;
}

.cn-hito {
    padding: var(--sp-2);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--surface-2);
}

.cn-hito-etq {
    font-size: var(--fs-xs);
    color: var(--text-muted);
}

.cn-hito-fecha {
    font-weight: 700;
    color: var(--text-strong);
}

.cn-hito-marca {
    font-size: var(--fs-2xs);
    font-weight: 400;
    font-style: normal;
    color: var(--text-muted);
}

.cn-hito-plan {
    margin-top: var(--sp-1);
    font-size: var(--fs-xs);
    color: var(--text);
}

/* ==========================================================================
   Exportaciones en segundo plano (components/descargas.py)
   --------------------------------------------------------------------------
   La pastilla es la version pequena del modal de progreso: sale al pulsar
   «Seguir trabajando» y se queda mientras haya algo en marcha o un resultado
   sin ver. Abajo en el CENTRO: a la derecha estan el boton del chat y el chip
   del mando, y a la izquierda, en escritorio, el pie de la navegacion.

   z-index 1062: por encima del modal del listado (fullscreen, 1055) para que
   se vea sin cerrarlo, y por debajo del modal de progreso (zindex=1070), que
   es la misma informacion en grande.
   ========================================================================== */

.descargas-pastilla {
    position: fixed;
    left: 50%;
    bottom: var(--sp-4);
    transform: translateX(-50%);
    z-index: 1062;
    display: inline-flex;
    align-items: center;
    /* Sin pisar el boton del chat (52 px + margen) a ningun lado. */
    max-width: calc(100vw - 2 * (var(--sp-4) + 64px));
    border: 1px solid var(--border-strong);
    border-radius: 999px;
    background: var(--surface-2);
    color: var(--text-strong);
    box-shadow: var(--shadow-md);
    font-size: var(--fs-sm);
    line-height: 1.2;
}

.descargas-pastilla-en-marcha { border-color: var(--gia-acento); }
.descargas-pastilla-listo { border-color: var(--est-bd-success); }
.descargas-pastilla-error,
.descargas-pastilla-interrumpido { border-color: var(--est-bd-danger); }

.descargas-pastilla-abrir,
.descargas-pastilla-cerrar {
    border: none;
    background: transparent;
    color: inherit;
    border-radius: 999px;
    cursor: pointer;
}

.descargas-pastilla-abrir {
    display: inline-flex;
    align-items: center;
    min-width: 0;
    padding: var(--sp-2) var(--sp-3);
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
    font-variant-numeric: tabular-nums;
}

.descargas-pastilla-cerrar {
    padding: var(--sp-2) var(--sp-3) var(--sp-2) var(--sp-1);
    color: var(--text-muted);
    font-size: var(--fs-xs);
}

.descargas-pastilla-abrir:hover,
.descargas-pastilla-cerrar:hover { color: var(--brand-ink); }

.descargas-pastilla-abrir:focus-visible,
.descargas-pastilla-cerrar:focus-visible {
    box-shadow: var(--focus-ring);
    outline: none;
}

/* ------------------------------------------------------- modal de progreso */

.descargas-item + .descargas-item {
    margin-top: var(--sp-4);
    padding-top: var(--sp-4);
    border-top: 1px solid var(--border);
}

.descargas-cabecera {
    display: flex;
    align-items: flex-start;
    gap: var(--sp-2);
    margin-bottom: var(--sp-2);
}

.descargas-icono {
    font-size: var(--fs-icono-lg);
    color: var(--brand-ink);
    line-height: 1.2;
}

.descargas-cabecera-texto { min-width: 0; }

.descargas-nombre {
    font-weight: 600;
    color: var(--text-strong);
    overflow-wrap: anywhere;
}

.descargas-etiqueta {
    font-size: var(--fs-sm);
    color: var(--text-muted);
}

.descargas-barra { height: 0.75rem; }

.descargas-fase {
    margin-top: var(--sp-2);
    font-size: var(--fs-sm);
    color: var(--text);
    font-variant-numeric: tabular-nums;
}

.descargas-error .descargas-fase,
.descargas-interrumpido .descargas-fase { color: var(--est-ink-danger); }

.descargas-aviso {
    margin-top: var(--sp-2);
    padding: var(--sp-2) var(--sp-3);
    border: 1px solid var(--est-bd-warning);
    border-radius: var(--radius-sm);
    background: var(--est-bg-warning);
    color: var(--est-ink-warning);
    font-size: var(--fs-sm);
}

.descargas-acciones {
    display: flex;
    justify-content: flex-end;
    gap: var(--sp-2);
    margin-top: var(--sp-3);
}

/* ==========================================================================
   Numero de fila tipo Excel y pie de las tablas (components/tablas.py)
   --------------------------------------------------------------------------
   La columna imita la cabecera de filas de Excel: fondo de cabecera, cifra
   gris y alineada a la derecha. Con filtros de columna los numeros tienen
   saltos (cada fila conserva el suyo) y se pintan en el color de marca, como
   el azul de Excel.
   ========================================================================== */

.ag-cell.celda-fila {
    background-color: var(--ag-header-background-color);
    border-right: 1px solid var(--border) !important;
    color: var(--text-muted);
    font-size: var(--fs-xs);
    font-variant-numeric: tabular-nums;
    text-align: right;
}

.ag-cell.celda-fila-filtrada {
    color: var(--brand-ink);
    font-weight: 600;
}

.ag-header-cell.celda-fila-cab {
    border-right: 1px solid var(--border);
}

.tabla-pie {
    min-height: 1.5em;
    padding: var(--sp-1) var(--sp-2) 0;
    font-size: var(--fs-xs);
    color: var(--text-muted);
    text-align: right;
    font-variant-numeric: tabular-nums;
}


/* ==========================================================================
   Graficos que filtran con doble clic (components/cruce.py, assets/cruce.js)
   --------------------------------------------------------------------------
   gia-filtra la pone ui.grafico cuando el grafico esta registrado. Plotly
   pinta el area de trazado con la clase cursor-crosshair (zoom por arrastre)
   y los donuts sin cursor: la mano avisa de que ahi se puede filtrar.

   El aviso sale abajo en el centro, un escalon por ENCIMA de la pastilla de
   exportaciones para no taparla si coinciden (descargas-pastilla, z-index
   1062), y no captura el raton: es un role=status que se lee y se va.
   ========================================================================== */

.gia-filtra .js-plotly-plot .plotly .cursor-crosshair,
.gia-filtra .js-plotly-plot .plotly .cursor-pointer,
.gia-filtra .js-plotly-plot .plotly .slice {
    cursor: pointer;
}

.gia-cruce-aviso {
    position: fixed;
    left: 50%;
    bottom: calc(var(--sp-4) + 3rem);
    z-index: 1063;
    max-width: calc(100vw - 2 * (var(--sp-4) + 64px));
    padding: var(--sp-2) var(--sp-3);
    border: 1px solid var(--gia-acento);
    border-radius: 999px;
    background: var(--surface-2);
    color: var(--text-strong);
    box-shadow: var(--shadow-md);
    font-size: var(--fs-sm);
    line-height: 1.3;
    text-align: center;
    pointer-events: none;
    opacity: 0;
    transform: translate(-50%, 0.5rem);
    transition: opacity 0.15s ease, transform 0.15s ease;
}

.gia-cruce-aviso-visible {
    opacity: 1;
    transform: translate(-50%, 0);
}

@media (prefers-reduced-motion: reduce) {
    .gia-cruce-aviso { transition: none; }
}
