/* Lenguaje visual de la app.
 *
 * Se carga DESPUÉS de Bootstrap 5.3.3 y lo redefine sobreescribiendo sus
 * variables CSS (--bs-*), que es lo que permite cambiar el aspecto de botones,
 * tablas y campos sin tocar ni un template.
 *
 * Lo cargan base.html y las cuatro pantallas que arman su propio <head>
 * (los tres auth/ y publico/privacidad). tests/test_estaticos_en_templates.py
 * garantiza que no se olvide ninguna.
 */

/* --- Tipografía ------------------------------------------------------------
   Inter, self-hosteada. `swap` y la pila del sistema como fallback: si el
   archivo no carga, la app degrada a lo que se veía antes en vez de quedarse
   sin texto. */
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 400 700;          /* variable: un archivo cubre todos los pesos */
  font-display: swap;
  src: url("fonts/inter-latin.woff2") format("woff2");
}

:root {
  --bs-body-font-family: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}

body { letter-spacing: -.005em; }

/* --- Paleta ----------------------------------------------------------------
   UN SOLO ACENTO. Antes convivían tres colores haciendo el mismo trabajo: el
   botón primario en grafito (--app-accion), los links y lo activo en celeste
   (--app-senal), y el celeste crudo del logo (--app-marca) de filete. Encima
   se filtraban cinco lugares con el #0d6efd de Bootstrap que nadie había
   elegido (ver "Fugas del azul de Bootstrap" más abajo). Ahora la acción
   principal, lo que señala y lo que se puede tocar salen del mismo tono.

   --app-acento es #0C6F9F: el celeste del isotipo bajado hasta 5.54:1 sobre
   blanco. Se prefirió a un #0E7DAF más cercano al logo porque ese da 4.59:1
   —pasa AA raspando— y el botón primario lleva texto de 14px, donde 4.5:1 es
   el piso y no la meta. Mismo criterio con el que se eligió --app-exito (ver
   abajo). Los dos celestes son indistinguibles a ojo; el margen no.

   Lo que se perdió a propósito: el color ya no distingue "esto ejecuta algo"
   de "esto te lleva a otro lado". Eso ahora lo dicen la sombra y el peso de
   .btn-primary. Era el precio de tener un solo acento y se pagó a sabiendas.

   Ver tests/test_paleta_contraste.py. */
:root {
  /* La familia completa del acento: pleno, hover, fondo teñido y filete. */
  --app-acento: #0C6F9F;
  --app-acento-hover: #0B6690;
  --app-acento-suave: #E6F3FA;
  --app-acento-borde: #9FD3EE;
  /* Los dos plenos otra vez, en decimal. Hace falta aparte porque `rgba()` no
     puede componerse a partir de un color hex, y hay dos consumidores que lo
     necesitan: el anillo de foco de los campos (que lleva canal alpha) y las
     utilidades de Bootstrap, que leen las variables *-rgb y no el hex. Si
     cambia un hex de acá arriba, cambia su decimal acá abajo. */
  --app-acento-rgb: 12, 111, 159;
  --app-acento-hover-rgb: 11, 102, 144;

  /* Los nombres viejos siguen vivos como alias. Se conservan porque los usa
     medio archivo y renombrarlos no movería un píxel; lo que cambió es que
     ahora los siete salen de la misma familia. Leídos hoy son la historia de
     la paleta: --app-accion era el grafito de la acción, --app-senal el
     celeste de lo activo, y eran dos colores distintos. */
  --app-accion: var(--app-acento);
  --app-accion-hover: var(--app-acento-hover);
  --app-senal: var(--app-acento);
  --app-senal-rgb: var(--app-acento-rgb);
  --app-senal-osc: var(--app-acento-hover);
  --app-senal-bg: var(--app-acento-suave);
  --app-senal-borde: var(--app-acento-borde);

  /* Gris carbón, sin cambios. No compite con el acento: #0C6F9F y #171B22 son
     dos colores distintos, no dos pasos del mismo, y se separan 3.12:1 — lo
     vigila test_el_boton_primario_no_se_pierde_contra_el_navbar. */
  --app-navbar: #171B22;

  /* El celeste crudo del isotipo, 1.9:1 sobre blanco. NO lo usa ninguna regla:
     el logo es logo.png y ya lo trae pintado, y el filete del sidebar —su
     último consumidor— directamente dejó de existir cuando el menú perdió las
     cajas (el activo se marca solo con el fondo). Queda declarado como
     referencia, que es lo que hay que mirar si alguna vez se rehace el isotipo
     o hace falta un acento sobre el navbar oscuro. Nunca como color de texto. */
  --app-marca: #3EBEFF;

  --app-texto: #1A2230;
  --app-texto-2: #5A6474;
  /* --app-rotulo da 3.06:1 sobre blanco: alcanza para 3:1 de componente no
     textual (WCAG), como el ícono de los botones fantasma de fila. NO sirve
     como color de texto (hace falta 4.5:1) — para eso está --app-rotulo-texto,
     un poco más oscuro, mismo tono gris azulado. Si estás por escribir
     `color: var(--app-rotulo)` sobre texto, es --app-rotulo-texto. */
  --app-rotulo: #8A94A4;
  /* Escala proporcional de --app-rotulo (mismo matiz, R/G/B en la misma razón)
     hasta el gris más claro de esa familia que sigue dando ≥4.6:1 sobre
     blanco: da 4.649:1. Un paso más claro (#6E7683) cae a 4.583:1, por debajo
     del margen pedido. Ver tests/test_paleta_contraste.py. */
  --app-rotulo-texto: #6D7582;

  --app-borde: #DFE3E9;
  /* El mismo borde, un paso más marcado, para cuando el mouse está encima.
     Estaba escrito tres veces como literal en botones y cápsulas. */
  --app-borde-fuerte: #C9CFD8;
  --app-borde-suave: #EEF1F4;
  --app-fondo: #F5F7F9;

  /* Botón neutro y hover de las acciones de fila. Van acá y no como literales
     sueltos en la regla de botones porque tests/test_paleta_contraste.py solo
     vigila tokens: un color que vive únicamente dentro de una regla puede
     bajar de AA sin que nada se entere (pasó con --app-exito, ver abajo). */
  --app-neutro-texto: #3D4757;

  --app-peligro: #C92A2A;
  --app-peligro-bg: #FDECEC;
  --app-peligro-borde: #F5CCCC;
  --app-alerta: #B45309;
  --app-alerta-bg: #FEF6E7;
  /* #15803D original daba 4.496:1 sobre --app-exito-bg — por debajo del
     4.5:1 de AA. #147D3B dobla la apuesta a 4.68:1, con margen real y no al
     raspar. No se tocó el fondo. */
  --app-exito: #147D3B;
  --app-exito-bg: #E8F6EC;
  --app-exito-borde: #BFE3CD;
  /* El ámbar no tenía borde propio y las plantillas escribían
     `border-warning-subtle` de Bootstrap, que no está afinado para esta
     paleta. Mismo criterio que --app-exito-borde y --app-peligro-borde. */
  --app-alerta-borde: #F3DFB8;

  /* Las mismas dos señales, atenuadas, para RELLENOS grandes: las barras del
     gráfico de Posición. El rojo y el verde plenos están calibrados para texto y
     botones, donde ocupan pocos píxeles; en una barra de doscientos píxeles de
     ancho gritan y le ganan al número que tienen al lado, que es lo que hay que
     leer.

     Los dos se aclaran en la MISMA medida (25 % de blanco) para no desbalancear
     uno contra el otro, que es lo que importa cuando conviven en el mismo gráfico.

     El piso acá no es 4,5:1 sino 3:1 —son elementos gráficos, no texto (WCAG
     1.4.11)—. Medido: el rojo aguanta hasta 35 % de blanco y el verde hasta 30 %
     antes de caer del piso. Se para un paso más acá, con 3,71:1 y 3,26:1, por el
     mismo criterio con el que se eligió --app-acento: el piso es el piso, no la
     meta. Ver tests/test_paleta_contraste.py. */
  --app-peligro-suave: #D65F5F;
  --app-exito-suave: #4F9E6C;
}

/* --- Escala tipográfica ----------------------------------------------------
   Siete pasos y ninguno más. En rem y no en px: el navegador tiene que poder
   escalar el texto, y el archivo entero ya estaba en rem.

   Antes había diez tamaños. Cuatro caían en fracciones —11.2, 14.4 y 18.4px—
   que no salían de ninguna decisión sino de haber escrito .7, .9 y 1.15rem a
   ojo; el quinto, 10.8px, era el contador del menú heredando el .75em del
   badge de Bootstrap desde un ítem de 14.4. Cada uno se movió al paso entero
   más cercano, así que el que más se corrió lo hizo 0.4px.

   El único tamaño que queda afuera a propósito es el .72rem de .celda, que es
   la grilla del cronograma. */
:root {
  --fs-micro:  .625rem;    /* 10 — solo donde el espacio manda: la grilla */
  --fs-rotulo: .6875rem;   /* 11 — versalitas, encabezados de tabla, contadores */
  --fs-ayuda:  .75rem;     /* 12 — etiquetas y textos de ayuda */
  --fs-menor:  .8125rem;   /* 13 — rótulos de campo, botones de fila */
  --fs-ui:     .875rem;    /* 14 — el tamaño de la interfaz: campos, botones, celdas */
  --fs-texto:  1rem;       /* 16 — texto corrido y títulos de tarjeta */
  --fs-titulo: 1.375rem;   /* 22 — el título de la pantalla */

  /* SIETE pasos y no seis, y el que sobra es el 12. Se evaluó sacarlo y no se
     puede: de sus 22 consumidores, 13 son las grillas del tablero y de Mercado,
     cuyos anchos en `rem` están MEDIDOS contra ese cuerpo (ver .tab-div-fila,
     .tab-casc-fila, .pos-fila). Subirlos a 13 los desborda, y bajarlos a 11 manda
     metadatos que hay que leer al tamaño reservado para versalitas.
     El 12 es «metadato dentro de un componente denso», y es un paso legítimo. */

  /* El ícono de las pantallas vacías. Existe porque `.fs-1` de Bootstrap es
     FLUIDO y va al revés: `calc(1.625rem + 4.5vw)` da 72px en un iPad de 1024 y
     40px en un monitor de 1440, o sea que el ícono se ACHICA cuando la pantalla
     crece. 40px es lo que se ve hoy en escritorio, así que fijarlo ahí no cambia
     nada arriba y arregla tablet y teléfono. */
  --fs-icono-vacio: 2.5rem; /* 40 */

  /* Las CIFRAS: los números grandes de los tableros y los mosaicos de resumen.
     Arriba de 22px la app no usaba tokens sino las utilidades `fs-*` de
     Bootstrap, en unos cuarenta lugares — o sea, una segunda escala, de otro
     sistema, para el texto más grande de la pantalla.

     Y no es solo que sea otra escala: `.fs-1` a `.fs-4` de Bootstrap son
     FLUIDAS (`calc(1.275rem + .3vw)` para fs-4, verificado en el
     bootstrap.min.css 5.3.3 del CDN). Recién a partir de 1200px se vuelven
     fijas. Es decir que los números más grandes de la app eran lo ÚNICO cuyo
     tamaño dependía del ancho de la ventana: un `fs-4` medía 21,5px en un
     teléfono de 360px y 24 en un monitor. Nadie decidió eso, viene de fábrica.

     Se fijan en los valores que Bootstrap usa arriba de 1200px, que son los que
     uno espera cuando lee "fs-4". Efecto visible: en un teléfono las cifras de
     los tableros quedan un par de píxeles más grandes que antes. */
  --fs-cifra-sm: 1.25rem;  /* 20 — fs-5 */
  --fs-cifra:    1.5rem;   /* 24 — fs-4, la más usada */
  --fs-cifra-lg: 1.75rem;  /* 28 — fs-3 */
  --fs-cifra-xl: 2rem;     /* 32 — fs-2 */
}

/* El `!important` no es negociable: las utilidades de Bootstrap lo traen, así
   que sin él no se pisan. `.fs-6` ya valía 1rem y se ata igual, para que las
   cinco salgan del mismo lugar. */
/* `.fs-1` no estaba atada y era la única medida de la app que dependía del ancho
   de ventana. Sus 14 usos son todos el ícono de una pantalla vacía. */
.fs-1 { font-size: var(--fs-icono-vacio) !important; }
.fs-2 { font-size: var(--fs-cifra-xl) !important; }
.fs-3 { font-size: var(--fs-cifra-lg) !important; }
.fs-4 { font-size: var(--fs-cifra) !important; }
.fs-5 { font-size: var(--fs-cifra-sm) !important; }
.fs-6 { font-size: var(--fs-texto) !important; }

/* --- Escala de forma -------------------------------------------------------
   Cuatro radios. Los valores de antes eran 6, 7.2, 8, 9.6 y 999px: la
   diferencia entre 7.2 y 8, o entre 8 y 9.6, no la ve nadie, pero obligaba a
   elegir cada vez que se escribía una regla. */
:root {
  --r-chip:    .375rem;    /* 6  — badges, etiquetas, ítems de menú */
  --r-control: .5rem;      /* 8  — botones y campos */
  --r-card:    .75rem;     /* 12 — tarjetas */
  --r-pill:    999px;      /* cápsulas de filtro */
}

/* --- Los colores de la grilla ----------------------------------------------
   Los grises de la hoja de calculo. Salian como hex sueltos del <style> que
   vivia adentro de cronogramas/grilla.html. */
:root {
  --grilla-encabezado: #EEF2F6;
  /* Un paso mas marcado que el fondo de la app: el fin de semana se tiene que
     ver de lejos, sin llegar a competir con el ambar del feriado. */
  --grilla-finde:      #EEF1F5;
  --grilla-feriado:    #FFF7E6;
}

/* --- El color de cada sector -----------------------------------------------
   Un filete de 3px en el chip de cada persona, con el color del sector del que
   VIENE. Cinco tonos desaturados a proposito: son cinco colores conviviendo en
   una grilla que ya tiene ambar para los feriados y gris para el fin de semana.
   Si gritaran, taparian eso.

   El mapa nombre -> token vive en `cronogramas/grilla.html` y no aca, porque los
   sectores son filas de la base y sus nombres los pone la organizacion. Un
   sector que no este en el mapa cae al gris del pool, que es el mismo caso que
   alguien sin sector: no hay nada que senalar. */
:root {
  --sector-pool: #C9CFD8;   /* sin sector: el pool rotante */
  --sector-1:    #7FA8C9;   /* azul apagado */
  --sector-2:    #8FBF9F;   /* verde salvia */
  --sector-3:    #D9A87C;   /* terracota claro */
  --sector-4:    #A99BC1;   /* lavanda gris */
  --sector-5:    #C99BA4;   /* rosa viejo */
}

/* Lo que Bootstrap lee de estas variables en tiempo de ejecución.
   Ojo: --bs-primary NO afecta a .btn-primary (Bootstrap compila el hex adentro
   de la clase), solo a .text-primary y .bg-primary. El botón principal se
   sigue definiendo aparte, en .btn-primary más abajo; desde la unificación los
   dos caminos llegan al mismo color, pero siguen siendo dos caminos. */
:root {
  /* De estas dos, la que hace el trabajo es la -rgb: en el CSS compilado de
     5.3.3 ninguna regla consume var(--bs-primary) directamente. Se deja igual
     porque es la variable semántica y la leen los componentes de terceros. */
  --bs-primary: var(--app-acento);
  --bs-primary-rgb: var(--app-acento-rgb);
  --bs-body-color: var(--app-texto);
  /* --bs-body-bg NO se toca, y el gris de la página lo pinta `body` unas
     líneas más abajo. Estuvo apuntando a --app-fondo (#F5F7F9) y el efecto era
     mucho más ancho que "el fondo de la página": Bootstrap 5.3.3 lo consume en
     --bs-table-bg, --bs-card-bg, --bs-list-group-bg, --bs-modal-bg,
     --bs-dropdown-bg, --bs-accordion-bg, --bs-popover-bg, --bs-offcanvas-bg,
     --bs-pagination-bg, --bs-nav-tabs-link-active-bg, --bs-form-check-bg y,
     como literal adentro de la regla, en el background de .form-control y
     .form-select. O sea: ninguna tabla, tarjeta, campo, modal ni ítem de menú
     de la app era blanco. En las tablas encima se leía peor que gris, porque
     --bs-table-border-color es --app-borde-suave (#EEF1F4), MÁS CLARO que el
     gris de la celda: las filas quedaban grises separadas por hilos blancos.
     Si alguna vez hace falta un fondo de página distinto, va en `body`. */
  --bs-border-color: var(--app-borde);
  /* Estas dos no son cosméticas: cambian el radio de botones, campos,
     tarjetas, badges, alertas, modales y dropdowns de toda la app de una vez. */
  --bs-border-radius: var(--r-control);
  --bs-border-radius-sm: var(--r-chip);
  /* Las -rgb son las que pintan los links (`a` lee rgba(var(--bs-link-color-rgb)...)),
     pero NO alcanza con ellas: --bs-nav-link-color sale de var(--bs-link-color)
     —la versión hex, sin -rgb—, y esa quedaba en el #0d6efd de Bootstrap. Por
     eso está el par completo: sin la línea de --bs-link-color, cada pestaña y
     cada píldora NO elegida de las barras de sección seguía azul Bootstrap al
     lado del acento nuevo. */
  --bs-link-color: var(--app-acento);
  --bs-link-color-rgb: var(--app-acento-rgb);
  --bs-link-hover-color: var(--app-acento-hover);
  --bs-link-hover-color-rgb: var(--app-acento-hover-rgb);
  /* La familia "subtle" de primary. Bootstrap la trae en su propio azul
     (#cfe2ff de fondo, #052c65 de texto), así que los badges escritos como
     `bg-primary-subtle text-primary` —hay una docena, en usuarios, performance,
     capacitaciones y objetivos— mezclaban nuestro celeste de texto con el
     lavanda de Bootstrap de fondo. Redirigidas acá, se arreglan todas juntas y
     de paso el acordeón de la ayuda, que lee estas dos para su panel abierto. */
  --bs-primary-text-emphasis: var(--app-acento-hover);
  --bs-primary-bg-subtle: var(--app-acento-suave);
  --bs-primary-border-subtle: var(--app-acento-borde);
  /* Baja a propósito: .text-muted pasa de ~6.7:1 a ~6.0:1 sobre blanco. Sigue
     pasando AA con margen y unifica el gris secundario con el resto de la
     paleta, pero es una baja de contraste, no una mejora. */
  --bs-secondary-color: var(--app-texto-2);
}

    body { background: var(--app-fondo); }
    /* 16px y no los 20 de Bootstrap: el nombre de la empresa está en todas las
       pantallas y no es lo que se viene a leer. Al bajarlo, el título de la
       pantalla —22px— pasa a ser lo más grande de la vista, que es lo correcto.
       El renglón de abajo ("Sistema de Gestión de Personal") ya está en 11px
       por `.txt-mini` en base.html; acá solo se le abre el tracking, que es lo
       que hace legible una línea chica. */
    .navbar-brand { font-size: var(--fs-texto); font-weight: 600; letter-spacing: .02em; }
    .navbar-brand .txt-mini { letter-spacing: .04em; }
    .celda { cursor: pointer; text-align: center; font-size: .72rem; padding: 2px 3px; min-width: 36px; }
    .celda:hover { opacity: .75; }
    .celda-manana      { background: #cfe2ff; }
    .celda-tarde       { background: #ffe0b2; }
    .celda-noche       { background: #b3c6e7; }
    .celda-cubrefranco { background: #e1bee7; }
    .celda-refuerzo    { background: #fff9c4; }
    .celda-manana_ext  { background: #b8d4ff; }
    .celda-tarde_ext   { background: #ffc87a; }
    .celda-manana_media{ background: #dcedc8; }
    .celda-franco      { background: #c8e6c9; }
    .celda-vacaciones  { background: #f5f5f5; }
    .celda-licencia    { background: #eeeeee; }
    .celda-vacia       { background: #ffcdd2; }
    .colaborador-col   { white-space: nowrap; min-width: 130px; }
    .grilla-wrap       { overflow-x: auto; }
    .alert-dismissible .btn-close { top: 50%; transform: translateY(-50%); padding-top: 0; padding-bottom: 0; }
    .fin-de-semana     { color: #d32f2f; }
    .page-card {
      background: #fff;
      border: 1px solid var(--app-borde);
      border-radius: var(--r-card);
      box-shadow: 0 1px 2px rgba(16, 24, 40, .05);
      padding: 1.75rem;
    }

    /* --- La tira de pantallas de una sección ----------------------------
       Tienda tiene ocho: cinco de consulta más las tres de definir, que van en la
       misma fila a propósito (ver `_base_tienda.html`). Al tamaño del cuerpo no
       entran, y la última caía sola a un segundo renglón —donde se lee como si
       fuera de otra cosa, no como la última de la fila—. Un paso más chica y con
       menos aire lateral entran las ocho sin tocarles el nombre a las pantallas,
       que es lo que las hace entendibles. En celular baja otro paso más, dentro
       de la media query de abajo. */
    .nav-tira .nav-link { font-size: var(--fs-ui); padding: .4rem .75rem; }

    /* --- Portal del colaborador en celular ------------------------------
       El portal se usa casi siempre desde un celular chico (~360px), donde una
       tabla de 4 o 5 columnas no entra y hay que arrastrarla de costado. Con
       .tabla-movil cada fila pasa a leerse como una tarjeta: se oculta el
       thead y cada celda muestra su rótulo desde data-label. Un solo mecanismo
       para todas las tablas del portal, en vez de duplicar el markup. */
    @media (max-width: 767.98px) {
      .tabla-movil, .tabla-movil tbody, .tabla-movil tr, .tabla-movil td { display: block; width: 100%; }
      .tabla-movil thead { display: none; }
      .tabla-movil > tbody > tr {
        background: #fff;
        border: 1px solid var(--bs-border-color);
        border-radius: var(--r-control);
        padding: .5rem .75rem;
        margin-bottom: .5rem;
      }
      .tabla-movil > tbody > tr.table-success { background: var(--bs-success-bg-subtle); }
      .tabla-movil > tbody > tr > td {
        background: transparent;
        border: 0;
        padding: .15rem 0;
        display: flex;
        justify-content: space-between;
        align-items: baseline;
        gap: 1rem;
      }
      /* Las filas de estado vacío usan colspan y no llevan rótulo. */
      .tabla-movil > tbody > tr > td:not([data-label])::before { content: none; }
      /* Una celda que no es un dato sino un bloque —un badge, una cifra y una frase
         de qué hacer, como el foco de la semana— no entra en el par rótulo/valor:
         partida en dos columnas queda con la mitad del ancho y tres renglones. Ocupa
         la tarjeta entera y apila lo suyo. */
      .tabla-movil > tbody > tr > td.celda-bloque {
        display: block;
        padding-top: .4rem;
      }
      /* Si esa celda de bloque además trae rótulo, el rótulo es un encabezado y no
         la primera palabra del contenido: sin esto, `Detalle` y `enfermedad`
         quedaban pegados en el mismo renglón como si fueran una sola frase. */
      .tabla-movil > tbody > tr > td.celda-bloque[data-label]::before {
        display: block;
        margin-bottom: .15rem;
      }
      /* Y si esa celda no tiene nada que decir, en la tarjeta no va: el guión que
         en la tabla evita que la columna se vea rota, acá es un encabezado
         anunciando un renglón vacío. Se decide con `:has` sobre la marca que ya
         pone la plantilla en vez de repetir la condición del guión en dos lados;
         donde `:has` no exista, se ve el guión, que es lo que se veía antes. */
      .tabla-movil > tbody > tr > td.celda-bloque:has(> * > .sin-detalle) { display: none; }
      /* La celda de identidad es el título de la tarjeta —es la única sin rótulo—,
         así que ahí pesa. En la tabla de escritorio sigue con el peso del cuerpo:
         alineada en una columna no necesita ayuda para encontrarse. */
      .tabla-movil > tbody > tr > td.celda-bloque > .celda-marcas > .nombre { font-weight: 600; }
      .tabla-movil > tbody > tr > td::before {
        content: attr(data-label);
        color: var(--bs-secondary-color);
        font-size: var(--fs-menor);
        flex: 0 0 auto;
      }
      /* Variante para la fila única de 12 meses (horas extras): como lista
         vertical serían 12 renglones, así que va como grilla de 4 por fila. */
      .tabla-movil-grid > tbody > tr {
        display: flex; flex-wrap: wrap; gap: .25rem;
        background: transparent; border: 0; padding: 0; margin: 0;
      }
      .tabla-movil-grid > tbody > tr > td {
        flex: 1 0 21%;
        display: block;
        text-align: center;
        border: 1px solid var(--bs-border-color);
        border-radius: var(--r-chip);
        padding: .3rem .15rem;
      }
      .tabla-movil-grid > tbody > tr > td::before { display: block; font-size: var(--fs-rotulo); }
    }

    /* La botonera del portal apilada ocupa 4-5 renglones en 360px y empuja los
       turnos abajo del pliegue. En celular pasa a mosaicos de ancho igual en
       una sola fila: ícono arriba, rótulo corto abajo. */
    @media (max-width: 575.98px) {
      /* Envuelve en vez de forzar una sola fila. Con `nowrap` y 6 o 7 accesos,
         cada mosaico quedaba de ~42px y el rótulo más largo ("Vacaciones", ~53px
         a .625rem) se desbordaba por los dos lados. El ancho mínimo de abajo es
         el que hace falta para que esa palabra entre con su padding, así que
         entran cuatro por fila y el resto pasa a una segunda. Dos filas de
         mosaicos siguen siendo mucho menos que los 4-5 renglones de botones
         apilados que este bloque vino a resolver. */
      .portal-acciones { width: 100%; flex-wrap: wrap !important; gap: .375rem !important; }
      .portal-acciones .btn {
        flex: 1 0 auto; min-width: 66px;
        display: flex; flex-direction: column; align-items: center; justify-content: center;
        gap: .15rem; padding: .5rem .35rem; line-height: 1.1;
        /* La cápsula de .chip son 999px, que en un mosaico de ~45x55 no da cápsula
           sino un círculo: con radio fijo el navegador escala las cuatro esquinas por
           un factor único, acotado a la mitad del lado menor. En escritorio el mismo
           999px sí funciona, porque ahí el acceso es ancho y bajo. */
        --bs-btn-border-radius: var(--r-control);
      }
      .portal-acciones .btn i { margin-right: 0 !important; font-size: 1.1rem; }
      .portal-acciones .btn .etiqueta { font-size: var(--fs-micro); text-align: center; }

      /* --- Ancho útil en el teléfono ----------------------------------------
         El 1,75rem de `.page-card` es la respiración de una pantalla de
         escritorio; en 390px se come 56 de los 390 y, con el padding de las
         tarjetas de adentro, el contenido termina viviendo en 252px —el 65% de
         la pantalla—. Con eso, una tabla de cuatro columnas que entraba justo
         pasa a arrastrarse de costado. Acá el margen lo pone la pantalla, no la
         tarjeta.

         Las secciones de gestión eran de escritorio, pero dejaron de serlo en
         cuanto un encargado con el módulo delegado (ver permisos por módulo)
         las abre desde el teléfono, que es como mira todo lo demás. */
      .page-card { padding: .75rem; }

      /* Las tablas densas de gestión —cinco y seis columnas de números— al
         tamaño del cuerpo no entran en ningún teléfono. A 13px, que es el paso
         de la escala chica, las de cuatro columnas entran enteras y las demás
         se acortan bastante. No se convierten en tarjetas (`tabla-movil`) a
         propósito: un ranking de vendedores partido en tarjetas deja de ser un
         ranking, y la comparación entre filas es justamente para qué se mira. */
      /* Tres clases en el selector y no una sola: la sección de tablas de más
         abajo fija el cuerpo de las celdas en `.table:not(.grilla-tabla)`, que
         es (0,2,0) y está DESPUÉS en el archivo. Un `.table-sm` pelado (0,1,0)
         perdería y el teléfono volvería a 14px. */
      .table.table-sm:not(.grilla-tabla) { font-size: var(--fs-menor); }

      /* --- La botonera de una sección, como tira ----------------------------
         Las pantallas de una sección (Resumen · El foco de la semana · Cómo vende
         la estación · Comparativo interanual, y las de Tienda) se apilaban en
         cuatro a siete renglones —unos 180px— antes de la primera cifra. En el
         teléfono pasan a una tira que se desliza de costado, con el elegido
         siempre a la vista: el que queda cortado a la derecha es lo que le avisa
         a la persona que hay más. */
      .nav-tira {
        flex-wrap: nowrap !important;
        overflow-x: auto;
        scrollbar-width: none;
        /* El scroll horizontal recorta la sombra del pill activo si no hay aire. */
        padding-bottom: .25rem;
      }
      .nav-tira::-webkit-scrollbar { display: none; }
      .nav-tira .nav-item { flex: 0 0 auto; }
      .nav-tira .nav-link {
        white-space: nowrap;
        font-size: var(--fs-menor);
        padding: .35rem .7rem;
      }

      /* Las siete barras de la semana (objetivos de Tienda) se reparten el ancho de
         la tarjeta: en el teléfono le tocan unos 40px a cada una, y a 14px el
         rótulo "L 24" se partía en dos renglones que le robaban la altura a la
         barra —que es lo único que la fila viene a mostrar—. */
      .barras-dia .small { font-size: var(--fs-rotulo); }
      .barras-dia .text-muted.small { font-size: var(--fs-micro); }
    }

/* --- Botones ---------------------------------------------------------------
   Tres pesos y no dos: la acción de la pantalla (grafito sólido), la acción
   neutra (blanca con borde, que antes era un gris apagado indistinguible), y
   la destructiva (roja, que tiene que seguir gritando).

   Todo se hace redefiniendo las variables --bs-btn-* que Bootstrap 5.3 ya lee
   para cada botón. No hace falta pelear especificidad ni tocar templates. */
.btn {
  --bs-btn-border-radius: var(--r-control);
  --bs-btn-font-weight: 500;
  transition: background-color .12s, border-color .12s, color .12s, box-shadow .12s;
}
.btn:active { transform: translateY(1px); }

.btn-primary {
  --bs-btn-bg: var(--app-accion);
  --bs-btn-border-color: var(--app-accion);
  --bs-btn-hover-bg: var(--app-accion-hover);
  --bs-btn-hover-border-color: var(--app-accion-hover);
  --bs-btn-active-bg: var(--app-accion-hover);
  --bs-btn-active-border-color: var(--app-accion-hover);
  --bs-btn-disabled-bg: var(--app-accion);
  --bs-btn-disabled-border-color: var(--app-accion);
  box-shadow: 0 1px 2px rgba(16, 24, 40, .12);
}

.btn-outline-secondary {
  --bs-btn-color: var(--app-neutro-texto);
  --bs-btn-bg: #fff;
  --bs-btn-border-color: var(--app-borde);
  --bs-btn-hover-color: var(--app-texto);
  --bs-btn-hover-bg: #F6F7F9;
  --bs-btn-hover-border-color: var(--app-borde-fuerte);
  --bs-btn-active-color: var(--app-texto);
  --bs-btn-active-bg: #F0F2F5;
  --bs-btn-active-border-color: var(--app-borde-fuerte);
  box-shadow: 0 1px 2px rgba(16, 24, 40, .05);
}

/* Botones de peligro CON TEXTO adentro de una tabla: un "Eliminar" a secas,
   sin ícono. Los de ícono solo ya eran fantasma por la regla de acá abajo;
   estos tres —admin/ayuda_portal/lista.html, admin/diferencias_caja/lista.html
   y admin/feriados/lista.html— seguían con el marco rojo entero, que en una
   columna de veinte filas son veinte rectángulos gritando lo mismo.

   Pierden el borde y se quedan con la palabra en rojo. El marco y el fondo
   teñido vuelven al pasar el mouse, que es cuando la advertencia sirve de algo
   y no antes.

   Va acotada a `td` a propósito y NO como regla global sobre
   .btn-outline-danger: en admin/colaboradores/formulario.html los sectores
   excluidos son `<label class="btn btn-outline-danger">` de un btn-check, y
   ahí el borde ES el estado prendido/apagado. Sacárselo los dejaría sin forma
   de mostrar cuál está elegido.

   No hace falta excluir los de ícono solo: la regla de abajo los agarra con
   especificidad (0,6,2) contra los (0,2,1) de ésta y gana por eso, no por
   orden — sobrevive a que alguien reordene el archivo. */
td .btn-outline-danger:not(.dropdown-toggle) {
  --bs-btn-color: var(--app-peligro);
  --bs-btn-bg: transparent;
  --bs-btn-border-color: transparent;
  --bs-btn-hover-color: var(--app-peligro);
  --bs-btn-hover-bg: var(--app-peligro-bg);
  --bs-btn-hover-border-color: var(--app-peligro-borde);
  --bs-btn-active-color: var(--app-peligro);
  --bs-btn-active-bg: var(--app-peligro-bg);
  --bs-btn-active-border-color: var(--app-peligro-borde);
  box-shadow: none;
}

/* --- Acciones de fila ------------------------------------------------------
   En una lista de admin, cada fila termina en dos o tres acciones (editar,
   deshabilitar, eliminar). Con botones de verdad, cinco filas son quince
   rectángulos y el marco pesa más que el contenido. Acá pierden el marco y
   quedan como íconos; el fondo aparece al pasar el mouse.

   Esto ANTES se hacía por selector y no por clase:

       td .btn:not(.dropdown-toggle):has(> i.bi:only-child:not(.me-1):not(.me-2))

   que trataba de adivinar "un botón cuyo único contenido es un ícono", y no se
   puede adivinar del todo: `:only-child` no cuenta el texto de al lado, así que
   había que excluir además los íconos con margen; el caret de un `dropdown-toggle`
   no es un hijo pero ocupa lugar, así que había que excluir esos botones; y todo
   eso vivía debajo de treinta líneas de comentario explicando por qué. Una clase
   puesta a mano no adivina nada: dice cuál es una acción de fila y cuál no.

   32×32 es el cuadrado mínimo cómodo para el mouse. Sigue por debajo de los 44px
   que WCAG pide para un objetivo táctil, y es a sabiendas: estas listas son de
   escritorio (el portal, que sí se usa en el teléfono, no tiene acciones de fila).
   Si alguna llega al celular, hay que agrandarlas ahí. */
.accion-fila {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  padding: 0;
  border: 0;
  border-radius: var(--r-chip);
  background: transparent;
  color: var(--app-texto-2);
  text-decoration: none;
  transition: background-color .12s, color .12s;
}
.accion-fila:hover,
.accion-fila:focus-visible {
  background: var(--app-fondo);
  color: var(--app-texto);
}
/* Lo que borra o da de baja. Mismo aspecto en reposo —no hay razón para que un
   tacho grite desde una lista de veinte filas— y el rojo aparece recién cuando
   el mouse está encima, que es cuando la persona está por decidir. Es la misma
   regla que ya usaban los botones de aprobar y rechazar. */
.accion-fila--peligro:hover,
.accion-fila--peligro:focus-visible {
  background: var(--app-peligro-bg);
  color: var(--app-peligro);
}
/* Las tres variantes que ya existían por color de borde, ahora por intención. */
.accion-fila--alerta:hover,
.accion-fila--alerta:focus-visible {
  background: var(--app-alerta-bg);
  color: var(--app-alerta);
}
.accion-fila--exito:hover,
.accion-fila--exito:focus-visible {
  background: var(--app-exito-bg);
  color: var(--app-exito);
}

/* La celda que las contiene: siempre la última, siempre a la derecha, siempre
   con 4px entre una y otra.

   El espaciado va por margen y no por `gap`, porque `gap` necesita que la celda
   sea flex y un `<td>` en `display: flex` se sale del layout de la tabla y
   desalinea el resto de la fila. Y la clase se pone a mano en el `<td>` en vez
   de deducirla con `:has()`: es la misma decisión que arriba — lo explícito no
   se equivoca. */
td.acciones {
  text-align: right;
  white-space: nowrap;
}
td.acciones > *:not(:first-child) {
  margin-left: 4px;
}

/* --- Tablas ----------------------------------------------------------------
   El encabezado deja de ser una franja gris y pasa a ser un rótulo chico sobre
   blanco con una línea abajo: el peso visual vuelve a los datos.

   La grilla del cronograma queda EXCLUIDA. Tiene su propio <style> en
   cronogramas/grilla.html con encabezados pegajosos, columnas de fin de semana
   y colores con significado; meterle mano desde acá la rompe.

   El padding no se hace con variables --bs-table-cell-padding-*: Bootstrap
   5.3.3 no las expone. Su CSS compilado trae el padding de
   `.table > :not(caption) > * > *` fijo en 0.5rem 0.5rem (verificado contra
   el bootstrap.css servido por jsDelivr); ahí solo color/fondo/borde de tabla
   son custom properties. Por eso el padding se pisa con un selector propio,
   más específico que el de Bootstrap, usando variables propias. */
.table:not(.grilla-tabla) {
  /* 14px y no los 16 del cuerpo: una lista de admin es una superficie para
     BARRER, no para leer de corrido. A 16px una tabla de seis columnas y veinte
     filas ocupa toda la pantalla y hay que scrollear para comparar dos valores,
     que es justo lo que se viene a hacer. El interlineado abre lo que el cuerpo
     achica. */
  font-size: var(--fs-ui);
  line-height: 1.4;
  --app-tabla-pad-y: .625rem;
  --app-tabla-pad-x: .75rem;
  --bs-table-border-color: var(--app-borde-suave);
  /* Reusa --app-fondo (#F5F7F9) y no un literal nuevo: el hover que se había
     probado, #F7F9FC, difiere de --app-fondo en 2-3/255 por canal —
     imperceptible— y ya hay dos grises casi idénticos tokenizados
     (--app-fondo, --app-borde-suave). Un tercero sin nombre no sumaba nada;
     además el matiz calza con la intención: la fila "retrocede" al tono de
     fondo de la página al pasar el mouse. */
  --bs-table-hover-bg: var(--app-fondo);
}

/* .table-sm y .tabla-mini son tablas de resumen: más aire las haría enormes.

   El `.table` de más en el selector no es redundante: sin él, esta regla y la
   de arriba tienen la misma especificidad (0,2,0) y pisan la misma variable, así
   que el padding compacto de una decena de tablas reales depende de que ésta
   quede DESPUÉS en el archivo. Con `.table` sube a (0,3,0) y gana por
   especificidad, que sobrevive a que alguien reordene las secciones. */
.table.table-sm:not(.grilla-tabla),
.table.tabla-mini:not(.grilla-tabla) {
  --app-tabla-pad-y: .45rem;
}

/* Guardado a min-width:768px (el corte "md" de Bootstrap, pareja del
   max-width:767.98px del bloque .tabla-movil de más arriba): por debajo, esa
   regla convierte cada <td> en una tarjeta con su propio padding (.15rem 0).
   Sin esta guarda, la regla de acá abajo tiene dos clases en el selector
   (.table + :not(.grilla-tabla)) contra la única clase de `.tabla-movil >
   tbody > tr > td`, le gana en especificidad pese a estar definida antes en
   el archivo, y rompe la vista de tarjetas en celular. */
@media (min-width: 768px) {
  .table:not(.grilla-tabla) > :not(caption) > * > * {
    padding: var(--app-tabla-pad-y) var(--app-tabla-pad-x);
  }
}

/* Es texto, no ícono: --app-rotulo-texto (4.65:1), nunca --app-rotulo
   (3.06:1, solo para íconos). Esto además le gana en especificidad a la regla
   local `.tabla-mini th { font-size: .78rem }` de cronogramas/grilla.html (que
   sigue viva para el `td` de esas mismas tablas, solo el `th` queda pisado) —
   si tocás ese <style> esperando que el font-size afecte el encabezado, es
   por esto. */
.table:not(.grilla-tabla) > thead > tr > th {
  color: var(--app-rotulo-texto);
  font-size: var(--fs-rotulo);
  font-weight: 650;
  text-transform: uppercase;
  letter-spacing: .06em;
  border-bottom: 1px solid var(--app-borde);
  /* El rótulo se pega a su línea y la línea se despega de la primera fila: es
     lo que hace que el encabezado se lea como el techo de la tabla y no como
     una fila más. */
  padding-bottom: .5rem;
}

/* La última fila no lleva línea: abajo ya está el borde de la tarjeta, y las
   dos juntas se ven como un renglón vacío. */
.table:not(.grilla-tabla) > tbody > tr:last-child > * {
  border-bottom-width: 0;
}

/* Números y fechas en cifras de ancho fijo, para que las columnas se puedan
   comparar de arriba a abajo sin que los dígitos bailen. Va sobre `.text-end`,
   que es lo que el admin ya escribe en toda columna de importe, días o fecha —
   así no hay que sumar una clase nueva a cada plantilla. En una columna de
   acciones alineada a la derecha no hay dígitos, así que no molesta. */
.table:not(.grilla-tabla) > :not(caption) > * > .text-end {
  font-variant-numeric: tabular-nums;
}

/* `thead class="table-light"` está escrito en 30 templates. En vez de sacarlo
   de todos, se neutraliza el gris que aporta. */
.table:not(.grilla-tabla) > thead.table-light {
  --bs-table-bg: transparent;
  --bs-table-color: var(--app-rotulo-texto);
}

/* --- Formularios -----------------------------------------------------------
   El apretado venía de dos lados: la etiqueta pegada al campo y las filas
   pegadas entre sí. Se separan las dos cosas. */
.form-label {
  font-size: var(--fs-menor);
  font-weight: 500;
  color: var(--app-texto-2);
  margin-bottom: .25rem;
}

/* La etiqueta de una opción es UI, no texto corrido: Bootstrap la deja heredando los
   16px del body y en un formulario queda más grande que el campo que la acompaña y
   que su propio rótulo (13px). 14px es el mismo tamaño que ya usan las tablas. */
.form-check-label {
  font-size: var(--fs-ui);
}

/* Un <legend> rotula un grupo de opciones y es el mismo rótulo que un .form-label,
   pero Bootstrap lo deja en calc(1.275rem + .3vw) —el doble— y se sale del tamaño
   que fijan los tokens: la pregunta quedaba más grande que el título de la tarjeta
   que la contiene. Va acá y no en la plantilla porque el tamaño no es una decisión
   de una pantalla. */
legend.form-label {
  font-size: var(--fs-menor);
  float: none;
  width: auto;
}

/* --app-borde-fuerte y no --app-borde: un campo tiene que decir que se escribe
   adentro. Con el borde suave —el mismo que separa dos filas de una tabla— y
   ahora además con el fondo blanco de la tarjeta detrás, el rectángulo
   desaparecía y quedaba un rótulo flotando sobre nada. */
.form-control,
.form-select {
  border-color: var(--app-borde-fuerte);
  color: var(--app-texto);
  box-shadow: 0 1px 2px rgba(16, 24, 40, .04);
}
/* Más claro que el texto real: el placeholder es una pista, no un valor. Pero
   --app-rotulo-texto y NO --app-rotulo: un placeholder es texto y necesita
   4.5:1. La revisión de diseño pedía --app-rotulo (3.06:1) y
   tests/test_paleta_contraste.py lo frenó. */
.form-control::placeholder {
  color: var(--app-rotulo-texto);
}
/* Bootstrap NO usa --bs-focus-ring-color acá: el foco de .form-control y
   .form-select está escrito a fuego adentro de la regla, `border-color:
   #86b7fe` + `box-shadow` azul (bootstrap.css, sin variable de por medio). No
   hay ninguna cascada de variables que resolver, se pisa directo. */
.form-control:focus,
.form-select:focus {
  border-color: var(--app-senal);
  box-shadow: 0 0 0 3px rgba(var(--app-senal-rgb), .14);
}

.form-check-input:checked {
  background-color: var(--app-senal);
  border-color: var(--app-senal);
}
.form-check-input:focus {
  border-color: var(--app-senal);
  box-shadow: 0 0 0 3px rgba(var(--app-senal-rgb), .14);
}

/* --- Tarjetas --------------------------------------------------------------
   Hoy flotan con sombra sola y los bordes de la página se pierden. Un borde de
   1px las apoya sin agregar peso. */
.card {
  --bs-card-border-color: var(--app-borde);
  --bs-card-border-radius: var(--r-card);
}
.card.border-0 {
  /* .border-0 está escrito en casi todas las tarjetas de la app y es una
     utilidad con !important, así que el borde se dibuja con box-shadow. */
  box-shadow: 0 0 0 1px var(--app-borde), 0 1px 2px rgba(16, 24, 40, .05) !important;
}
.card-header {
  /* Bootstrap 5.3.3 sí define el padding de .card-header con variables
     (--bs-card-cap-padding-y/-x, ver bootstrap.css línea ~4460-4461) — a
     diferencia del padding de .table, que está compilado como literal. Se
     pisan las variables en vez de escribir un padding literal para seguir el
     mismo mecanismo que ya usa el resto del archivo (.btn, .card, .table) y
     no competir en especificidad con la regla de Bootstrap. */
  --bs-card-cap-padding-y: .7rem;
  --bs-card-cap-padding-x: 1rem;
  /* El encabezado se tiñe y el cuerpo queda blanco. Hasta acá era al revés sin
     que nadie lo hubiera decidido: los templates escribían `bg-white` en el
     encabezado y el cuerpo heredaba el gris de --bs-body-bg (ver el comentario
     de la sección de variables). Con el cuerpo ya blanco, si el encabezado
     también lo fuera la sección desaparecería adentro de la .page-card, que es
     blanca: la banda gris es lo que la separa. */
  --bs-card-cap-bg: var(--app-fondo);
  /* Explícito y no heredado de --bs-card-border-width: casi todas las tarjetas
     de la app llevan `.border-0`, y el día que eso llegue al encabezado la
     sección se queda sin línea. */
  border-bottom: 1px solid var(--app-borde);
}
/* El rótulo de sección ("DATOS BÁSICOS", "LÍMITES"). Vive acá y no en las
   plantillas: las 63 que lo escribían repetían `bg-white border-bottom
   fw-semibold small text-uppercase text-muted`, seis utilidades para decir una
   cosa, y cuatro de ellas llevan !important, así que mientras estuvieran en el
   markup ninguna regla de este archivo podía cambiarlas.

   El tracking va atado a .text-uppercase y no a .card-header a secas: abre la
   versalita y se lee mejor, pero en minúsculas desarma la palabra. Hay al
   menos un header sin mayúsculas (el nombre de sector en dotacion/lista.html)
   que con tracking se vería separado de más. */
.card-header.text-uppercase {
  font-size: var(--fs-rotulo);
  font-weight: 700;
  letter-spacing: .08em;
  color: var(--app-rotulo-texto);
}
/* El ícono del rótulo no baja con el texto: a 11px es una mancha. Un paso más
   grande alcanza para que se lea como marca de la sección. */
.card-header.text-uppercase .bi {
  font-size: var(--fs-ayuda);
}

/* --- Badges ----------------------------------------------------------------
   Un badge dice EN QUÉ ESTADO está algo. Se escribe con UNA clase —
   `badge text-bg-<color>-subtle`— y nada más: el color, el fondo y el borde
   salen de la definición de acá abajo.

   Antes se escribían de 71 maneras distintas repartidas en 160 usos
   (`bg-success-subtle text-success border border-success-subtle`,
   `text-bg-success-subtle`, con y sin `-emphasis`, con y sin `border`…), que
   era el punto más desparejo de la app y además dejaba el color en manos de
   Bootstrap, cuyos tonos no están afinados para esta paleta.

   Lo que NO es un estado no va acá: para rotular un dato —"portal", el sector,
   "ext", "½j", "op 41"— están `.etiqueta-dato` y `.etiqueta-codigo`, más
   abajo.

   El peso se dejó en el 700 de Bootstrap a propósito. Bajarlo a 500 llegaba a
   la grilla del cronograma, que no se toca en todo este trabajo: el marcador
   "F" de feriado es un `.badge` sin estilo inline que lo proteja. */
.badge {
  --bs-badge-border-radius: var(--r-chip);
}

/* --- Badges teñidos --------------------------------------------------------
   `text-bg-<color>-subtle` NO existe en Bootstrap. Las clases suaves de 5.3 son
   `bg-<color>-subtle` para el fondo y `text-<color>-emphasis` para el texto, por
   separado; `text-bg-<color>` (sin "subtle") sí existe pero es el color pleno.

   La app las venía usando igual, en badges de media docena de pantallas, y el
   resultado era invisible: sin fondo declarado, el `.badge` se queda con su
   `color` propio, que es BLANCO. Los que se leían eran los que además traían un
   `text-*-emphasis` puesto a mano al lado; los demás llevaban tiempo en blanco
   sobre blanco.

   Se definen acá en vez de corregir cada plantilla porque son quince usos y el
   nombre ya está en el vocabulario del proyecto. Los colores salen de los tokens
   de la app —los mismos pares que `tests/test_paleta_contraste.py` verifica que
   pasen AA— y no de los de Bootstrap, que no están afinados para esta paleta. */
.text-bg-primary-subtle,
.text-bg-info-subtle {
  color: var(--app-senal-osc);
  background-color: var(--app-senal-bg);
  border: 1px solid var(--app-senal-borde);
}
.text-bg-secondary-subtle {
  color: var(--app-neutro-texto);
  background-color: var(--app-borde-suave);
  border: 1px solid var(--app-borde);
}
.text-bg-success-subtle {
  color: var(--app-exito);
  background-color: var(--app-exito-bg);
  border: 1px solid var(--app-exito-borde);
}
.text-bg-warning-subtle {
  color: var(--app-alerta);
  background-color: var(--app-alerta-bg);
  border: 1px solid var(--app-alerta-borde);
}
.text-bg-danger-subtle {
  color: var(--app-peligro);
  background-color: var(--app-peligro-bg);
  border: 1px solid var(--app-peligro-borde);
}

/* --- Etiquetas de dato -----------------------------------------------------
   La otra mitad de lo que hoy se escribe como badge no es un estado: es un
   rótulo que acompaña a un dato — de qué sector es alguien, que entró "por el
   portal", que el turno es extendido o de media jornada, el código de operario
   del ERP. Un badge teñido les da un color que no significa nada; lo que
   necesitan es leerse como una etiqueta y desaparecer.

   Neutras a propósito y sin `.badge` de por medio: no heredan ni el 700 ni el
   .75em de Bootstrap, que a esta altura ya achicaban de más.

   El nombre NO es `.etiqueta` a secas: esa clase ya existe y es el rótulo de
   los mosaicos de la botonera del portal (ver `.portal-acciones .btn
   .etiqueta` más arriba y `portal/_base_portal.html`). */
.etiqueta-dato,
.etiqueta-codigo {
  display: inline-block;
  padding: 1px 8px;
  border-radius: var(--r-chip);
  font-size: var(--fs-ayuda);
  font-weight: 500;
  line-height: 1.5;
  color: var(--app-texto-2);
  background-color: var(--app-fondo);
  border: 1px solid var(--app-borde);
}
/* Para lo que es un código y no una palabra: legajos, códigos de turno, "op N".
   La monoespaciada es lo que deja compararlos de un vistazo en una columna. */
.etiqueta-codigo {
  font-family: var(--bs-font-monospace);
}

/* --- Jerarquía tipográfica -------------------------------------------------
   Títulos, rótulos y datos pesaban casi lo mismo.

   `h4` es el TÍTULO DE PANTALLA y solo eso: los 51 que hay en la app siguen el
   mismo patrón —el ícono, el nombre de la pantalla, y abajo un `.small
   .text-muted` con el recuento— y ninguno vive adentro de una tarjeta. Por eso
   se puede vestir desde acá sin tocar una sola plantilla. Los títulos de
   tarjeta son `h5`. Si alguna vez hace falta un `h4` que no sea el título de
   la pantalla, la regla deja de valer y hay que darle una clase. */
h4 {
  font-size: var(--fs-titulo);
  font-weight: 700;
  letter-spacing: -.01em;
  line-height: 1.2;
}
h5 {
  font-size: var(--fs-texto);
  font-weight: 600;
  letter-spacing: -.015em;
}

/* --- Navbar ----------------------------------------------------------------
   El único lugar donde el celeste del isotipo va crudo: sobre el navbar oscuro
   sí contrasta, y ata la barra al logo que tiene al lado. */
.navbar.bg-dark {
  background: var(--app-navbar) !important;
}
.navbar .btn-outline-light {
  --bs-btn-border-color: rgba(255, 255, 255, .35);
  --bs-btn-color: #F1F3F5;
  --bs-btn-hover-bg: rgba(255, 255, 255, .1);
  --bs-btn-hover-border-color: rgba(255, 255, 255, .5);
  --bs-btn-hover-color: #fff;
  --bs-btn-border-radius: var(--r-control);
}

/* 56px fijos. La marca son dos renglones (16px + 11px con `lh-sm`) = ~34px, así
   que quedan 11px de aire arriba y abajo: entra, pero justo. Si alguna vez el
   rótulo de abajo crece o se le suma algo a la barra, esto se nota antes que
   nada — es el número a revisar. */
.navbar-app {
  min-height: 56px;
  padding-top: 0;
  padding-bottom: 0;
}
.navbar-rotulo {
  font-weight: 400;
  letter-spacing: .04em;
  opacity: .7;
}

/* Navegación, no acciones. Antes eran cuatro botones con borde idénticos, así
   que ir a Cronogramas se veía igual de "ejecutable" que salir de la sesión. Un
   link no lleva marco; lo único que se dibuja es dónde estás parado. */
.navbar-link {
  display: inline-flex;
  align-items: center;
  padding: 8px 12px;
  border: 0;
  border-radius: var(--r-chip);
  background: transparent;
  color: rgba(255, 255, 255, .8);
  font-size: var(--fs-ui);
  font-weight: 500;
  text-decoration: none;
  transition: background-color .12s, color .12s;
}
.navbar-link:hover,
.navbar-link:focus-visible {
  background: rgba(255, 255, 255, .06);
  color: #fff;
}
.navbar-link.activo {
  background: rgba(255, 255, 255, .10);
  color: #fff;
}

/* El disparador de la cuenta: el círculo con las iniciales más el nombre. */
.navbar-cuenta {
  display: inline-flex;
  align-items: center;
  gap: .5rem;
  max-width: min(45vw, 240px);
  padding: 4px 8px;
  border: 0;
  border-radius: var(--r-pill);
  background: transparent;
  color: rgba(255, 255, 255, .8);
  font-size: var(--fs-ui);
  transition: background-color .12s, color .12s;
}
.navbar-cuenta:hover,
.navbar-cuenta:focus-visible,
.navbar-cuenta[aria-expanded="true"] {
  background: rgba(255, 255, 255, .10);
  color: #fff;
}
/* El caret de Bootstrap sobra: el círculo ya dice que hay algo abajo, y con el
   nombre truncado al lado son tres cosas peleando por el mismo ancho. */
.navbar-cuenta.dropdown-toggle::after { display: none; }
/* --app-marca es el celeste crudo del logo: 1.9:1 sobre blanco, inservible como
   color de texto, pero acá es FONDO y lleva texto oscuro encima. Es el segundo
   lugar de la app donde el celeste del isotipo aparece tal cual, y por eso ata
   el avatar a la marca de al lado. */
.navbar-avatar {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: var(--app-marca);
  color: var(--app-navbar);
  font-size: var(--fs-ayuda);
  font-weight: 700;
  letter-spacing: .02em;
}
.navbar-cuenta-nombre {
  /* min-width:0 es lo que deja encoger al flex item para que `text-truncate`
     corte un nombre largo en vez de desbordar la barra. */
  min-width: 0;
}
/* El botón de "Salir" vive adentro de un <form> para que siga siendo POST, y un
   form es un bloque: sin esto la entrada del menú queda más angosta que las
   otras y con el texto corrido. */
.dropdown-menu form { margin: 0; }
.dropdown-menu form > .dropdown-item {
  width: 100%;
  border: 0;
  background: transparent;
  text-align: left;
}

/* --- Menú de admin ---------------------------------------------------------
   Una lista de links y nada más. Venía armado con el `list-group` de Bootstrap,
   que le dibujaba a cada ítem su propio borde de caja: veinte entradas eran
   veinte rectángulos apilados, una escalera de cajas al costado de la única
   cosa de la pantalla que sí es una tarjeta. Ahora el menú no tiene fondo ni
   marco —flota sobre el gris de la página— y lo único que se dibuja es dónde
   está parada la persona.

   Las clases son propias (`.sidebar-item`, `.sidebar-grupo`) y no de Bootstrap:
   pelear contra los defaults de `.list-group-item` costaba más reglas que
   escribir las cuatro que hacen falta. Ver `admin/_base_admin.html`. */
.sidebar-nav {
  display: flex;
  flex-direction: column;
}
/* Cada grupo es un <details>; el título, su <summary>. Uno solo abierto por
   vez (el de la pantalla actual): con los cinco desplegados el menú mide más de
   1000px y las últimas entradas quedan abajo del pliegue. */
.sidebar-grupo {
  margin-top: .375rem;
}
.sidebar-grupo:first-child { margin-top: 0; }

.sidebar-grupo-titulo {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: .5rem;
  padding: .375rem .75rem;
  border-radius: var(--r-chip);
  font-size: var(--fs-rotulo);
  font-weight: 650;
  text-transform: uppercase;
  letter-spacing: .08em;
  /* --app-rotulo-texto y no --app-rotulo: es texto, necesita 4.5:1. */
  color: var(--app-rotulo-texto);
  cursor: pointer;
  /* El triangulito propio de <summary>: se va y lo reemplaza el chevron de
     abajo, que sigue el mismo lenguaje que el resto de la app. Hacen falta las
     dos líneas — `list-style` cubre a los navegadores al día y el
     ::-webkit-details-marker a los Safari viejos. */
  list-style: none;
}
.sidebar-grupo-titulo::-webkit-details-marker { display: none; }
.sidebar-grupo-titulo:hover { color: var(--app-texto); }
/* El foco es visible porque el título se maneja con el teclado: Enter y Espacio
   lo despliegan, y sin esto no se ve dónde está parado el cursor. */
.sidebar-grupo-titulo:focus-visible {
  outline: 2px solid var(--app-senal);
  outline-offset: -2px;
}
/* El chevron va como pseudo-elemento y no como <i> en la plantilla: es
   decoración del estado abierto/cerrado, no contenido, y así no lo lee un
   lector de pantalla (que ya anuncia el <summary> como "contraído/expandido"). */
.sidebar-grupo-titulo::after {
  content: "";
  flex: 0 0 auto;
  width: 6px;
  height: 6px;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: rotate(-45deg);
  transition: transform .15s;
  opacity: .7;
}
.sidebar-grupo[open] > .sidebar-grupo-titulo::after {
  transform: rotate(45deg);
}

.sidebar-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: .5rem;
  min-height: 36px;
  padding: 0 .75rem;
  border-radius: var(--r-chip);
  font-size: var(--fs-ui);
  font-weight: 500;
  color: var(--app-texto-2);
  text-decoration: none;
}
/* El ícono no se achica con el texto: a 14px se vuelve una mancha y es la mitad
   de lo que hace reconocible una entrada del menú de un vistazo. */
.sidebar-item .bi {
  margin-right: .5rem;
  font-size: var(--fs-texto);
  opacity: .8;
}
.sidebar-item:hover {
  background: var(--app-fondo);
  color: var(--app-texto);
}
/* Fondo de señal y nada más: el filete celeste de la izquierda era el recurso
   para marcar el activo cuando cada ítem tenía su caja. Sin cajas, el fondo
   solo ya lo dice, y el filete quedaba como un resto.

   --app-senal-osc y no --app-senal: es el par que tests/test_paleta_contraste.py
   verifica contra --app-senal-bg. */
.sidebar-item.activo {
  background: var(--app-senal-bg);
  color: var(--app-senal-osc);
  font-weight: 600;
}
/* El contador de pendientes. Ámbar y no rojo: son novedades esperando que
   alguien las mire, no un error. */
.sidebar-contador {
  flex: 0 0 auto;
  padding: 1px 7px;
  border-radius: var(--r-pill);
  font-size: var(--fs-rotulo);
  font-weight: 700;
  color: var(--app-alerta);
  background: var(--app-alerta-bg);
}

/* --- Fugas del azul de Bootstrap -------------------------------------------
   Pisar --bs-primary y --bs-primary-rgb no alcanza para todos los componentes:
   varios traen el #0d6efd COMPILADO adentro de su propia variable, así que la
   cascada de --bs-primary no los toca nunca. Verificado leyendo el
   bootstrap.min.css 5.3.3 de jsDelivr, que es el que carga base.html.

   Son los últimos lugares donde el azul de otra paleta se veía al lado del
   acento de ésta. Los otros tres se tapan desde :root (--bs-link-color, la
   familia --bs-primary-*-subtle) y desde la regla del sidebar de acá arriba;
   estos dos necesitan regla propia porque sus variables viven en el
   componente, no en :root. Las siete variables involucradas las vigila
   test_las_fugas_del_azul_de_bootstrap_siguen_tapadas. */

/* --bs-nav-pills-link-active-bg: #0d6efd, literal. Es la píldora ELEGIDA de la
   navegación de sección de Tienda (tienda/_base_tienda.html) y de Objetivos
   (objetivos/_base_objetivos.html): el elemento más saturado de esas
   pantallas, y el que más lejos quedaba del resto de la paleta. El texto sigue
   siendo el #fff que pone Bootstrap — 5.54:1 sobre el acento. */
.nav-pills {
  --bs-nav-pills-link-active-bg: var(--app-acento);
}

/* El acordeón de portal/ayuda.html. El fondo y el texto del panel abierto ya
   viajan solos, porque salen de --bs-primary-bg-subtle y
   --bs-primary-text-emphasis (redirigidos en :root). Lo que no viaja es el
   chevron: son dos SVG incrustados como data-uri con el color escrito adentro
   (#212529 cerrado, #052c65 abierto) y un data-uri no admite var().

   En vez de duplicar un hex del acento adentro de una URL —donde ningún test
   lo puede vigilar y se desincroniza en el primer ajuste—, el estado abierto
   reusa el ícono del cerrado. La flecha ya gira 180°: el color no estaba
   diciendo nada que la rotación no dijera. */
.accordion {
  --bs-accordion-btn-active-icon: var(--bs-accordion-btn-icon);
}

/* --- Cápsulas de elección --------------------------------------------------
   Filtros y navegación de sección: se ven distintos de los botones que
   ejecutan algo, porque no ejecutan nada.

   Se aplica ENCIMA de `btn btn-sm`, no en lugar de. El portal depende de
   `.portal-acciones .btn` para armar los mosaicos de celular (ver más arriba en
   este mismo archivo); si se le saca la clase `.btn`, esa regla deja de tomar y
   la botonera desborda en 360px. */
.chip {
  --bs-btn-border-radius: var(--r-pill);
  --bs-btn-color: var(--app-texto-2);
  --bs-btn-bg: #fff;
  --bs-btn-border-color: var(--app-borde);
  --bs-btn-hover-color: var(--app-texto);
  /* --app-fondo (#F5F7F9) difiere del #F6F7F9 que se había probado en 1/255
     por canal: imperceptible, mismo caso que el hover de tabla y el de
     sidebar de más arriba. Se reusa el token en vez de sumar un gris suelto
     más. */
  --bs-btn-hover-bg: var(--app-fondo);
  --bs-btn-hover-border-color: var(--app-borde-fuerte);
  --bs-btn-font-weight: 500;
  padding: .3rem .8rem;
  box-shadow: none;
}
.chip.on {
  --bs-btn-color: var(--app-senal-osc);
  --bs-btn-bg: var(--app-senal-bg);
  --bs-btn-border-color: var(--app-senal-borde);
  --bs-btn-hover-color: var(--app-senal-osc);
  --bs-btn-hover-bg: var(--app-senal-bg);
  --bs-btn-hover-border-color: var(--app-senal-borde);
  --bs-btn-font-weight: 600;
}

/* Franja de herramientas: los filtros dejan de ser un bloque de formulario
   suelto y pasan a una franja propia. Es lo que hace que la tarjeta se lea
   como una pieza y no como cosas apiladas. */
.barra-herramientas > .card-body {
  /* Ahora sí --app-fondo. El #FBFCFD que había acá se eligió para leerse "un
     paso por debajo de la tarjeta blanca" en una época en la que la tarjeta NO
     era blanca: con el cuerpo en #F5F7F9, ese casi-blanco resaltaba. Corregido
     el fondo de las tarjetas, #FBFCFD contra blanco es 3/255 por canal y la
     franja desaparecía. Es la misma banda que el encabezado de una tarjeta, y
     usa el mismo gris. */
  background: var(--app-fondo);
  border-radius: var(--r-card);
  padding: .75rem 1rem;
}
/* La línea separa la franja de lo que venga después, adentro de la misma
   tarjeta. Hoy la franja es lo único que hay en .barra-herramientas (el card
   solo tiene este card-body), así que la línea se apaga: si no, cae justo
   encima del borde inferior que ya dibuja el box-shadow de la tarjeta y
   duplica el trazo. */
.barra-herramientas > .card-body:not(:last-child) {
  border-bottom: 1px solid var(--app-borde);
}
/* El chevron del botón que pliega los filtros en el teléfono. Girarlo es lo que
   distingue "esto se abre" de "esto es una etiqueta". */
.barra-herramientas .giro-al-abrir {
  display: inline-block;
  transition: transform .15s ease-in-out;
}
.barra-herramientas [aria-expanded="true"] .giro-al-abrir { transform: rotate(180deg); }

.barra-herramientas .rotulo-filtro {
  font-size: var(--fs-rotulo);
  text-transform: uppercase;
  letter-spacing: .07em;
  font-weight: 650;
  /* --app-rotulo-texto y NO --app-rotulo: el segundo da 3.06:1, que alcanza
     para un ícono pero no para texto. */
  color: var(--app-rotulo-texto);
}

/* --- Una sola escala de control --------------------------------------------
   El tamaño de un campo o de un botón se decide ACÁ y no en cada template.

   Antes lo decidía quien escribía la pantalla, eligiendo entre la variante
   chica y la completa de Bootstrap, y el recuento decía lo que se veía: 95
   campos completos contra 27 chicos, 38 desplegables completos contra 16, y al
   revés en los botones, 161 chicos contra 75 completos. Peor: la mezcla pasaba
   adentro de la misma pantalla (colaboradores/formulario tenía 11 completos y
   36 chicos), así que la app cambiaba de proporción al navegar.

   Las dos variantes rinden igual y el que escribe un template puede seguir
   poniendo `-sm` o no: ya no cambia nada. El aire lo da el padding, no el
   cuerpo de la letra — por eso el padding es MAYOR que el de `-sm` original,
   aunque el texto sea el chico. */
.btn,
.btn-sm, .btn-group-sm > .btn,
.btn-lg, .btn-group-lg > .btn {
  --bs-btn-font-size: var(--fs-ui);
  --bs-btn-padding-y: .45rem;
  --bs-btn-padding-x: .9rem;
}

.form-control, .form-control-sm, .form-control-lg {
  font-size: var(--fs-ui);
  padding: .45rem .75rem;
}

/* El desplegable lleva su propio padding a la derecha: ahí vive la flecha que
   Bootstrap dibuja como imagen de fondo. Si se le pasa el atajo de dos valores,
   el texto de la opción más larga se le mete abajo. */
.form-select, .form-select-sm, .form-select-lg {
  font-size: var(--fs-ui);
  padding: .45rem 2.25rem .45rem .75rem;
}

/* --- Ayuda flotante --------------------------------------------------------
   Ayuda dejó de ser un mosaico más de la botonera del portal. No es una sección
   que la gente entre a hacer —como pedir un franco o mirar sus turnos—, es lo
   que se busca cuando algo no se entiende, así que compite mal en una fila de
   accesos y encima les robaba ancho.

   Blanco con borde y no un círculo de color: tiene que estar disponible sin
   pedir atención. La sombra es la que lo despega del fondo, que es casi del
   mismo tono. 44px es el mínimo que WCAG pide para un objetivo táctil. */
.ayuda-flotante {
  position: fixed;
  left: 1rem;
  bottom: 1rem;
  z-index: 1030;              /* sobre el contenido, debajo de los modales (1055) */
  width: 44px;
  height: 44px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: #fff;
  border: 1px solid var(--app-borde);
  color: var(--app-senal);
  font-size: 1.15rem;
  text-decoration: none;
  box-shadow: 0 2px 8px rgba(16, 24, 40, .12);
  transition: background-color .12s, border-color .12s, color .12s, box-shadow .12s;
}
.ayuda-flotante:hover,
.ayuda-flotante:focus-visible {
  background: var(--app-senal-bg);
  border-color: var(--app-senal-borde);
  color: var(--app-senal-osc);
  box-shadow: 0 4px 12px rgba(16, 24, 40, .16);
}
/* Estando en la propia pantalla de ayuda queda marcado, igual que el mosaico
   activo de la botonera: si no, no hay forma de saber dónde estás parado. */
.ayuda-flotante.on {
  background: var(--app-senal-bg);
  border-color: var(--app-senal-borde);
  color: var(--app-senal-osc);
}
/* El contenido termina justo donde arranca el botón, así que lo último de la
   página quedaba tapado al llegar al final del scroll. El `:has` acota el
   margen a las pantallas que efectivamente tienen el botón: las de admin usan
   el mismo .page-card y ahí no hay nada flotando que las tape. */
.page-card:has(~ .ayuda-flotante) { margin-bottom: 4rem; }

/* El encabezado de un sector en Requisitos de dotación, que es un <summary>.
   Mismo chevron y mismo comportamiento que los grupos del menú; se le saca el
   triangulito propio y se le agrega el cursor, que es lo que dice que se toca. */
.sector-dotacion > summary {
  cursor: pointer;
  list-style: none;
}
.sector-dotacion > summary::-webkit-details-marker { display: none; }
.sector-dotacion > summary::after {
  content: "";
  flex: 0 0 auto;
  width: 6px;
  height: 6px;
  margin-left: .5rem;
  border-right: 1.5px solid var(--app-rotulo);
  border-bottom: 1.5px solid var(--app-rotulo);
  transform: rotate(-45deg);
  transition: transform .12s;
}
.sector-dotacion[open] > summary::after { transform: rotate(45deg); }
/* Plegado, el encabezado ES la tarjeta: sin esto le queda la esquina de abajo
   cuadrada contra el borde redondeado del <details>. */
.sector-dotacion:not([open]) > summary {
  border-bottom: 0;
  border-radius: var(--r-card);
}

/* --- Código --------------------------------------------------------------
   Bootstrap le pone a `<code>` un rosa fuerte (#d63384) que no sale de ninguna
   decisión de esta paleta y es, de lejos, lo más saturado de la pantalla donde
   aparece: en el panel de WhatsApp y en Performance, donde `<code>` marca un
   nombre de archivo o un identificador, o sea lo MENOS importante de la frase.
   Acá es gris sobre gris, que es lo que corresponde a un dato técnico. */
code {
  color: var(--app-texto-2);
  background: var(--app-fondo);
  padding: 1px 5px;
  border-radius: var(--r-chip);
}

/* --- Transiciones ----------------------------------------------------------
   Lo que cambia de color al pasar el mouse lo hace en 120ms en vez de saltar.
   Es la única animación de la app y no mueve nada de lugar: solo color.

   `prefers-reduced-motion` no es decorativo. Hay gente a la que el movimiento
   en pantalla le da mareo o dolor de cabeza, y el sistema operativo ya tiene
   una casilla donde lo dice; respetarla es leerla. */
.sidebar-item,
.sidebar-grupo-titulo,
.table:not(.grilla-tabla) > tbody > tr {
  transition: background-color .12s, border-color .12s, color .12s;
}
/* `.btn` y `.accion-fila` ya traen la suya más arriba, y la de `.btn` incluye
   además `box-shadow` —el botón principal tiene sombra y también se atenúa—.
   Repetirlas acá les ganaría por orden y les sacaría esa parte. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    transition-duration: .01ms !important;
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    scroll-behavior: auto !important;
  }
}

/* --- Celda con marcas ------------------------------------------------------
   Un dato a la izquierda y sus marcas pegadas al borde derecho de la columna.

   El caso es la columna Colaborador de Novedades: el nombre, y al lado el chip
   "portal" —la novedad la cargó la persona desde su portal— y el clip del
   certificado. Escritas una atrás de otra caían donde terminara el nombre, así
   que la misma marca aparecía en una posición distinta en cada fila. Pegadas a
   la derecha, la columna vuelve a poder barrerse de arriba a abajo: o hay algo
   en esa posición o no lo hay.

   El flex va en un <div> adentro del <td> y no en el <td>: un `display: flex`
   sobre una celda le saca el `display: table-cell` y el ancho fijo de la
   columna (`table-layout: fixed`) deja de aplicarle. */
.celda-marcas {
  display: flex;
  align-items: center;
  gap: .5rem;
}
.celda-marcas > .nombre {
  /* min-width:0 es lo que deja encoger al flex item: sin eso un nombre largo
     empuja las marcas fuera de la columna en vez de partirse. */
  flex: 1 1 auto;
  min-width: 0;
}
.celda-marcas > .marcas {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: .375rem;
}

/* --- La grilla del cronograma ----------------------------------------------
   Estuvo fuera de este archivo hasta ahora: su CSS vivia en un <style> adentro
   de `cronogramas/grilla.html`, con once colores escritos como hex a mano. Era
   la ultima isla afuera del sistema.

   Sigue siendo una superficie aparte —es una hoja de calculo, no una lista— y
   por eso conserva su propio ritmo. Lo que cambia es que los colores salen de
   los mismos tokens que el resto.

   Y la grilla es UNA tabla para todos los sectores, no una por sector. Con una
   tabla por sector, cada una traia su propio encabezado pegajoso y al bajar se
   iban pegando encabezados distintos, uno por vez. */
.grilla-wrap {
  position: relative;
  max-height: 80vh;
  overflow: auto;
}
.grilla-tabla {
  border-collapse: separate;
  border-spacing: 0;
}
.grilla-tabla td,
.grilla-tabla th {
  border: none;
  border-bottom: 1px solid var(--app-borde-suave);
}

/* --- Lo que queda fijo al scrollear ---
   Tres niveles de z-index, y el orden importa: la esquina tiene que quedar
   arriba de la fila de dias Y de la columna de turnos, o al moverse en diagonal
   una de las dos la tapa. */
.grilla-tabla thead th {
  position: sticky;
  top: 0;
  z-index: 3;
  background: var(--grilla-encabezado);
  color: var(--app-neutro-texto);
  font-weight: 600;
  text-align: center;
  vertical-align: bottom;
  border-bottom: 2px solid var(--app-borde-fuerte);
  padding: 6px 4px;
}
.grilla-tabla thead th small {
  color: var(--app-rotulo-texto);
  font-weight: 500;
}
/* La columna de la izquierda: el nombre del turno. Antes esta celda estaba
   VACIA —el rotulo iba en una banda con colspan arriba de la fila— asi que
   apenas se scrolleaba de costado no quedaba nada que dijera que turno era. */
.grilla-tabla .col-rotulo {
  position: sticky;
  left: 0;
  z-index: 2;
  background: #fff;
  text-align: left;
  font-size: var(--fs-ayuda);
  font-weight: 600;
  color: var(--app-neutro-texto);
  padding: 6px 8px;
  /* El filete separa la columna fija del contenido que le pasa por debajo. */
  border-right: 1px solid var(--app-borde);
}
.grilla-tabla thead th.col-rotulo {
  z-index: 4;
  background: var(--grilla-encabezado);
}

/* La banda del sector: ocupa la fila entera y tambien se queda fija a la
   izquierda, para no perder de vista de que sector es lo que se esta mirando. */
.grilla-tabla .fila-sector td {
  background: var(--grilla-encabezado);
  color: var(--app-texto);
  font-size: var(--fs-ayuda);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .06em;
  padding: 8px;
  border-bottom: 2px solid var(--app-borde-fuerte);
}
/* El sticky va en el <span> y no en la celda: la celda ocupa el ancho entero de
   la tabla, así que fijarla no haría nada — el texto se iría igual al correr la
   grilla de costado. Es el nombre lo que tiene que quedarse. */
.grilla-tabla .fila-sector td > span {
  position: sticky;
  left: 8px;
  display: inline-block;
}

/* --- La leyenda ---
   Tres colores y un ícono que hasta ahora no estaban explicados en ninguna
   parte de la pantalla. */
.grilla-leyenda {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .25rem 1rem;
  padding: .625rem .25rem 0;
  font-size: var(--fs-ayuda);
  color: var(--app-texto-2);
}
.grilla-leyenda > span {
  display: inline-flex;
  align-items: center;
  gap: .375rem;
}
.muestra-sector {
  width: 3px;
  height: 14px;
  border-radius: 2px;
  background: var(--sector-pool);
}
.muestra-sector.sector-1 { background: var(--sector-1); }
.muestra-sector.sector-2 { background: var(--sector-2); }
.muestra-sector.sector-3 { background: var(--sector-3); }
.muestra-sector.sector-4 { background: var(--sector-4); }
.muestra-sector.sector-5 { background: var(--sector-5); }

/* --- Los dias ---
   El fin de semana es un tono mas marcado que el resto, en el encabezado y en
   las celdas: es el ritmo de la semana y se tiene que ver de lejos. Antes el
   encabezado del sabado solo cambiaba el color del TEXTO.

   `!important` porque compite con el `background` del `thead th` de arriba, que
   tambien lo lleva. */
.grilla-tabla .col-finde { background: var(--grilla-finde) !important; }
.grilla-tabla .col-feriado { background: var(--grilla-feriado) !important; }
.grilla-tabla thead th.col-finde { color: var(--app-rotulo-texto); }
.grilla-tabla tbody tr:not(.fila-sector):hover td:not(.col-rotulo) {
  background-color: var(--app-fondo);
}

/* --- El chip de una persona ---
   Se queda con el `badge` de Bootstrap y no pasa a `.etiqueta-dato`: medido, la
   etiqueta aprieta el alto y en una celda con tres nombres apilados ese aire es
   lo unico que los separa. */
/* Todos del mismo ancho: el de la celda. Sueltos, cada chip medía lo que medía
   su nombre, así que una columna con cuatro personas eran cuatro rectángulos de
   anchos distintos, centrados, y el ojo no encontraba ninguna línea vertical
   donde apoyarse. Al ancho de la celda, la columna vuelve a ser una columna.

   El alto sí cambia —un nombre largo se parte en dos renglones— y está bien: eso
   es contenido. Lo que no aportaba nada era que cambiara el ancho. */
.grilla-tabla td .badge {
  display: block;
  width: 100%;
  margin-bottom: 3px;
  white-space: normal;
  text-align: center;
  line-height: 1.2;
  font-size: var(--fs-ayuda) !important;
  padding: .28em .4em;
}
.grilla-tabla td .badge:last-child { margin-bottom: 0; }
/* El borde izquierdo dice de que sector VIENE la persona, que no siempre es
   aquel donde aparece. Un chip con el filete de Tienda adentro de la grilla de
   Playa es alguien que vino a cubrir: es la informacion que antes habia que
   sacar cruzando dos pantallas. */
.chip-colab {
  border-left: 3px solid var(--sector-pool) !important;
}
.chip-colab.sector-1 { border-left-color: var(--sector-1) !important; }
.chip-colab.sector-2 { border-left-color: var(--sector-2) !important; }
.chip-colab.sector-3 { border-left-color: var(--sector-3) !important; }
.chip-colab.sector-4 { border-left-color: var(--sector-4) !important; }
.chip-colab.sector-5 { border-left-color: var(--sector-5) !important; }

/* El sufijo de variante del turno ("ext", "medio dia"): era un <small> en
   italica ambar suelto al lado del nombre, o sea texto que parecia una nota al
   pie. Es un dato del turno y se ve como tal. */
.chip-turno {
  display: inline-block;
  padding: 0 4px;
  border-radius: var(--r-chip);
  background: var(--app-alerta-bg);
  color: var(--app-alerta);
  font-size: var(--fs-micro);
  font-weight: 600;
  line-height: 1.5;
}

/* El encargado del turno. Era un circulo negro de 7px con `title`: invisible
   para un lector de pantalla (un <span> vacio no se anuncia), sin leyenda en
   ninguna parte y dificil de apuntar con el mouse. La llave es literal: el
   encargado es quien ABRE el turno. */
/* Va ADENTRO del chip y no al lado. Afuera le comía ancho a la celda, corría el
   chip de la persona encargada respecto de los demás de la columna, y quedaba
   como un objeto suelto flotando entre dos nombres. Adentro pertenece a la
   persona, que es lo que significa. */
.marca-encargado {
  margin-right: .2em;
  font-size: .9em;
  color: var(--app-texto);
  vertical-align: baseline;
}

/* --- El borde del scroll ---
   La grilla de un periodo de cuatro semanas mide casi 4.000px: siempre hay algo
   afuera y nada lo decia. El degradado aparece mientras quede contenido a la
   derecha y se apaga al llegar al final, que es lo que lo vuelve informacion en
   vez de decoracion. */
.grilla-wrap::after {
  content: "";
  position: sticky;
  top: 0;
  right: 0;
  float: right;
  width: 28px;
  height: 80vh;
  margin-left: -28px;
  pointer-events: none;
  background: linear-gradient(to right, rgba(16, 24, 40, 0), rgba(16, 24, 40, .10));
  opacity: 1;
  transition: opacity .15s;
}
.grilla-wrap.sin-scroll-derecha::after { opacity: 0; }

/* --- Volver ----------------------------------------------------------------
   Salir de una pantalla no es una acción sobre lo que la pantalla muestra, así
   que no compite en la barra con Aprobar o Descargar: va a la izquierda del
   título, que es donde se lo busca, y como link. */
.link-volver {
  display: inline-flex;
  align-items: center;
  gap: .25rem;
  flex: 0 0 auto;
  font-size: var(--fs-ui);
  font-weight: 500;
  color: var(--app-texto-2);
  text-decoration: none;
}
.link-volver:hover { color: var(--app-texto); }

/* --- Panel de violaciones --------------------------------------------------
   Agrupadas por regla: un período con 27 violaciones son casi siempre tres o
   cuatro reglas repetidas, y la lista plana repetía el mismo título en negrita
   una y otra vez.

   Y cada ítem deja el fondo ámbar entero. Veintisiete bloques teñidos son una
   pared de color donde nada resalta —que es lo contrario de lo que tiene que
   hacer una alerta—. Ahora el fondo es blanco y el color queda en un filete de
   3px a la izquierda: rojo si es dura (impide aprobar) y ámbar si es blanda. */
.grupo-violacion + .grupo-violacion { margin-top: .25rem; }
.grupo-violacion > summary {
  display: flex;
  align-items: center;
  gap: .5rem;
  padding: .25rem .375rem;
  border-radius: var(--r-chip);
  cursor: pointer;
  list-style: none;
  font-size: var(--fs-ayuda);
  font-weight: 600;
  color: var(--app-texto);
}
.grupo-violacion > summary::-webkit-details-marker { display: none; }
.grupo-violacion > summary:hover { background: var(--app-fondo); }
.grupo-violacion > summary:focus-visible {
  outline: 2px solid var(--app-senal);
  outline-offset: -2px;
}
.grupo-violacion-nombre {
  /* `flex: 1` y NO `justify-content: space-between` en el summary. Con
     space-between el espacio sobrante se reparte ENTRE los tres elementos
     (chevron, nombre, cuenta), así que cada nombre arrancaba a una distancia
     distinta del chevron según cuánto midiera: los títulos quedaban
     desalineados entre sí. Haciendo crecer al nombre, el sobrante queda todo
     de un lado y los tres arrancan en la misma x.

     El `min-width: 0` es lo que deja que un nombre largo ("Refuerzo prioritario
     sin cubrir") se parta en vez de empujar la cuenta fuera del panel. */
  flex: 1 1 auto;
  min-width: 0;
}
.grupo-violacion-cuenta {
  flex: 0 0 auto;
  padding: 0 6px;
  border-radius: var(--r-pill);
  background: var(--app-borde-suave);
  color: var(--app-neutro-texto);
  font-size: var(--fs-rotulo);
  font-weight: 700;
}
/* El chevron, igual que en el menú de admin y en Dotación. */
.grupo-violacion > summary::after {
  content: "";
  order: -1;
  flex: 0 0 auto;
  width: 5px;
  height: 5px;
  border-right: 1.5px solid var(--app-rotulo);
  border-bottom: 1.5px solid var(--app-rotulo);
  transform: rotate(-45deg);
  transition: transform .12s;
}
.grupo-violacion[open] > summary::after { transform: rotate(45deg); }

.violacion {
  margin: .25rem 0 .25rem .5rem;
  padding: .25rem .5rem;
  background: #fff;
  border-left: 3px solid var(--app-alerta);
  border-radius: 0 var(--r-chip) var(--r-chip) 0;
}
/* La dura SÍ lleva fondo teñido, y es la única. Es al revés de lo que había
   antes —donde las veintisiete estaban en ámbar y ninguna resaltaba— y ese es
   justamente el punto: el tinte vale cuando lo lleva una sola. Un filete rojo
   contra uno ámbar, los dos de 3px, no alcanza para distinguirlas cuando no hay
   una al lado de la otra con qué comparar. */
.violacion--dura {
  border-left-color: var(--app-peligro);
  background: var(--app-peligro-bg);
}

/* En el resumen del grupo, sin desplegar: lo que hace dura a una regla es que
   IMPIDE APROBAR el período, así que eso es lo que dice el rótulo. "Dura" es el
   nombre interno; el que mira el panel quiere saber qué le bloquea el trabajo. */
.marca-dura {
  flex: 0 0 auto;
  padding: 0 6px;
  border-radius: var(--r-pill);
  background: var(--app-peligro-bg);
  color: var(--app-peligro);
  font-size: var(--fs-rotulo);
  font-weight: 600;
  text-transform: none;
  letter-spacing: 0;
}
.grupo-violacion--dura > summary > .grupo-violacion-cuenta {
  background: var(--app-peligro-bg);
  color: var(--app-peligro);
}
.grupo-violacion--dura > summary > .grupo-violacion-nombre {
  color: var(--app-peligro);
}

/* --- Estado vacío ----------------------------------------------------------
   Una tabla sin filas decía "Sin dotación configurada." en gris, centrado, y
   nada más: la misma frase valía para "todavía no cargaste nada" y para "el
   filtro no encontró nada", que son dos situaciones distintas y una de las dos
   tiene algo para hacer.

   El ícono es lo que hace que se lea como un estado y no como un error, y el
   link de abajo es la diferencia: aparece solo cuando la pantalla tiene una
   acción de crear, y lleva al mismo lado que el botón de arriba. Donde no hay
   nada para crear —Auditoría, un reporte, un filtro sin resultados— no se pone
   un link que no lleve a ningún lado. */
.estado-vacio {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: .375rem;
  padding: 20px;
  color: var(--app-texto-2);
  font-size: var(--fs-ui);
  text-align: center;
}
.estado-vacio > .bi {
  font-size: 24px;
  line-height: 1;
  /* --app-rotulo y no --app-rotulo-texto: acá sí es un ícono, no texto, y para
     un componente no textual WCAG pide 3:1, que es lo que da. */
  color: var(--app-rotulo);
}
.estado-vacio a {
  font-size: var(--fs-menor);
  font-weight: 500;
}

/* --- Escala de texto chico -------------------------------------------------
   Cuatro pasos, en píxeles enteros: 10, 11, 12 y 13. Arriba de eso mandan
   `.small` de Bootstrap (14) y los encabezados.

   Antes había catorce tamaños distintos escritos a mano adentro de los
   templates (.55, .62, .65, .68, .70, .72, .75, .78, .80, .82, .85rem, y
   algunos repetidos con otra notación), decididos pantalla por pantalla. Doce
   de ellos vivían en la grilla del cronograma. Cada valor se mapeó al paso más
   cercano, así que el que más se movió lo hizo 1.2px y el resto menos de 0.6px:
   la intención es unificar el vocabulario, no rediseñar nada. */
.txt-micro { font-size: var(--fs-micro); }   /* 10px */
.txt-mini  { font-size: var(--fs-rotulo); }  /* 11px */
.txt-chico { font-size: var(--fs-ayuda); }    /* 12px */
.txt-menor { font-size: var(--fs-menor); }  /* 13px */

/* El verde y el rojo de la paleta como color de texto. Bootstrap trae
   `.text-success` y `.text-danger`, pero apuntan a SUS colores, que no son los
   que este archivo verifica contra AA. */
.text-exito   { color: var(--app-exito); }
.text-peligro { color: var(--app-peligro); }

/* Una tarjeta de las que se cuentan de a varias —un mes, un objetivo— cuando esa
   cumplió. Tiñe el recuadro entero en vez de agregarle una marca: en una fila de seis,
   lo que se busca es cuáles están logradas, y eso se ve de un vistazo por el fondo, no
   leyendo seis renglones que dicen «sí» o «no».

   Usa los tokens de la paleta y no `bg-success-subtle` de Bootstrap: el par
   --app-exito sobre --app-exito-bg es el que tests/test_paleta_contraste.py verifica
   contra AA. Va con `border`, que toma el color de acá. */
.tarjeta-exito {
  background-color: var(--app-exito-bg);
  border-color: var(--app-exito-borde) !important;
}

/* Columna de números: alineada a la derecha y con cifras de ancho fijo, para
   poder barrerla de arriba a abajo.

   Convive con la regla de `.text-end` de la sección de tablas, que hace lo
   mismo pero por posición. Ésta es la que se pone a propósito: dice "esto es un
   número", y por eso también sirve fuera de una tabla —en una definición, en
   una tarjeta— donde `.text-end` no llega. */
.col-num {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

/* --- Acciones de fila con texto --------------------------------------------
   Segundo escalón de la escala de control, y el único: los campos y los botones
   de una pantalla van al tamaño de arriba; los botones que se repiten UNA VEZ
   POR RENGLÓN van acá, más chicos.

   El motivo no es estético sino de proporción: un botón de formulario aparece
   dos o tres veces en la pantalla, y uno de fila aparece tantas veces como
   filas haya. Al mismo tamaño, veinte "Aprobar" pesan más que todo el contenido
   que uno está tratando de leer.

   No llevan ícono adentro a propósito: la palabra ya dice qué hacen y el ícono
   solo agregaba ancho. */
.btn-fila {
  --bs-btn-padding-y: .2rem;
  --bs-btn-padding-x: .55rem;
  --bs-btn-font-size: var(--fs-menor);
}

/* Acá vivían `.btn-aprobar` y `.btn-rechazar`: los dos blancos, con el borde de
   su color, tiñéndose recién al pasar el mouse. Existían para que aprobar y
   rechazar pesaran IGUAL —"no hay una respuesta correcta que la interfaz deba
   sugerir"— y se borraron a pedido el 2026-09-06.

   Ahora Aprobar es el botón principal de la fila y Rechazar es neutro. El motivo
   es de negocio y no de estilo: en la práctica casi todo se aprueba, y hacer que
   el camino frecuente sea el obvio ahorra un clic por novedad. Queda anotado
   porque es un cambio de criterio, no una limpieza: si alguna vez rechazar deja
   de ser la excepción, esto hay que revisarlo.

   Lo que NO cambió: rechazar sigue a la vista en la fila (no se escondió en el
   menú de "⋯") y su confirmación sigue exigiendo motivo. */

/* La confirmación del rechazo sí va plena: ya no es "abrir el formulario", es
   el acto. Es el único rojo sólido de la pantalla. */
.btn-rechazar-firme {
  --bs-btn-color: #fff;
  --bs-btn-bg: var(--app-peligro);
  --bs-btn-border-color: var(--app-peligro);
  --bs-btn-hover-color: #fff;
  --bs-btn-hover-bg: #A82121;
  --bs-btn-hover-border-color: #A82121;
}

/* --- Chips de estado del mismo ancho ---------------------------------------
   Un badge crece con su texto, así que "Pendiente" y "Rechazada" terminaban de
   anchos distintos y la columna quedaba con el borde derecho dentado. Con un
   ancho mínimo común y el texto centrado, la columna se lee como una sola cosa
   y el ojo puede barrerla de arriba a abajo.

   Dos anchos y no uno: los de la columna Estado son palabras sueltas, y los del
   circuito de cambio de franco llegan a "esperando respuesta". Cada grupo se
   alinea consigo mismo, que es lo que importa: viven en columnas distintas. */
.chip-estado,
.chip-propuesta {
  display: inline-block;
  text-align: center;
}
.chip-estado { min-width: 5.75rem; }
.chip-propuesta { min-width: 9.5rem; }

/* --- Posición: una barra contra una referencia -----------------------------
   La pantalla de Mercado compara mi variación contra la del mercado, por
   producto. Se dibuja con UNA barra —la mía— y una marca donde está el
   mercado, y no con dos barras enfrentadas, por dos razones.

   La primera es de lectura: si la barra pasa la marca caí más que el mercado,
   y eso se ve sin leer un número. La segunda es de paleta: esta app tiene un
   solo acento (ver la cabecera de este archivo), así que una segunda serie
   pediría un color que competiría con el primero. Y el mercado no es una serie
   par de la mía: es la vara contra la que me mido, y una vara va en gris.

   Las cinco filas comparten escala. Normalizar cada una por su cuenta dibujaría
   una caída del 6 % igual que una del 24 %. */
.pos-cuadro { display: grid; row-gap: .5rem; }

.pos-fila {
  display: grid;
  grid-template-columns: 7.5rem minmax(8rem, 1fr) 4.5rem 11rem;
  align-items: center;
  column-gap: .75rem;
}

.pos-nombre { font-size: var(--fs-ui); font-weight: 500; }
.pos-cifra { font-size: var(--fs-ui); font-weight: 600; text-align: right; }
.pos-referencia { font-size: var(--fs-ayuda); color: var(--app-rotulo-texto); }

.pos-pista {
  position: relative;
  height: 14px;
  background: var(--app-borde-suave);
  border-radius: var(--r-chip);
}

/* La barra divergente: nace en el cero del centro y crece para un lado. La dirección
   es la codificación principal —rojo y verde son el par que peor distingue un daltónico,
   así que no pueden ser lo único que diga el signo— y el color es el refuerzo. */
.pos-dif {
  position: absolute;
  top: 0;
  bottom: 0;
  min-width: 2px;               /* media décima de punto tiene que verse igual */
}
/* Redondeado solo en el extremo del DATO; el que nace en el cero va recto, apoyado
   sobre la línea base. Redondear los dos lados hace que la barra parezca flotar en vez
   de nacer del cero, que es de donde nace. */
.pos-dif.peor {
  background: var(--app-peligro-suave);
  border-radius: var(--r-chip) 0 0 var(--r-chip);
}
.pos-dif.mejor {
  background: var(--app-exito-suave);
  border-radius: 0 var(--r-chip) var(--r-chip) 0;
}

/* El cero. Va por encima de las barras y con el anillo de la superficie, porque es la
   referencia contra la que se leen todas y tiene que verse aunque una barra lo cruce. */
.pos-cero {
  position: absolute;
  left: 50%;
  top: -3px;
  bottom: -3px;
  width: 1px;
  /* Un paso más marcado que el borde de la pista: es la referencia contra la que se
     leen las cinco filas, y con el gris del borde apenas se distinguía del fondo. */
  background: var(--app-rotulo);
  z-index: 1;
}

.pos-cifra.peor { color: var(--app-peligro); }
.pos-cifra.mejor { color: var(--app-exito); }

/* En pantalla chica la grilla de cuatro columnas no entra: el nombre y la cifra
   pasan arriba, y la barra ocupa el ancho. */
@media (max-width: 767.98px) {
  .pos-fila {
    grid-template-columns: 1fr auto;
    row-gap: .25rem;
    padding-bottom: .5rem;
    border-bottom: 1px solid var(--app-borde-suave);
  }
  .pos-pista { grid-column: 1 / -1; }
  .pos-cifra { text-align: left; }
  .pos-referencia { text-align: right; }
}

/* El <details> de «ver el detalle» dibuja su propio triángulo además del ícono que
   pone la plantilla, y quedaban dos flechas. */
details > summary { list-style: none; }
details > summary::-webkit-details-marker { display: none; }
details[open] > summary .bi-chevron-right { transform: rotate(90deg); }
details > summary .bi-chevron-right { transition: transform .15s ease; }

/* --- Los tiles de Posición -------------------------------------------------- */
.pos-rotulo {
  font-size: var(--fs-rotulo);
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--app-rotulo-texto);
  font-weight: 600;
}
.pos-valor {
  font-size: var(--fs-cifra);
  font-weight: 700;
  letter-spacing: -.01em;
  margin: .25rem 0 .125rem;
  font-variant-numeric: tabular-nums;
}
.pos-valor small { font-size: var(--fs-ayuda); font-weight: 500; color: var(--app-rotulo-texto); }
.pos-delta { font-size: var(--fs-ayuda); font-weight: 600; font-variant-numeric: tabular-nums; }
.pos-delta.mejor { color: var(--app-exito); }
.pos-delta.peor { color: var(--app-peligro); }
.pos-contexto { font-size: var(--fs-ayuda); color: var(--app-rotulo-texto); }

/* --- La ficha de una boca --------------------------------------------------- */
/* La serie como dibujo y no como veinte pares fecha-valor en texto corrido. Cada
   producto lleva SU escala vertical: van de 12 a 400 m3 según el producto, y una escala
   común dejaría tres de las cuatro líneas pegadas al piso. Por eso no se dibuja el eje:
   lo que se lee es la forma, y los dos números que importan están etiquetados al lado. */
.boca-series { display: grid; row-gap: .75rem; }
.boca-serie {
  display: grid;
  grid-template-columns: 8rem minmax(8rem, 1fr) 14rem;
  align-items: center;
  column-gap: 1rem;
}
.boca-serie-nombre { font-size: var(--fs-ui); font-weight: 500; }
.boca-chispa {
  width: 100%;
  height: 28px;
  overflow: visible;
}
.boca-chispa polyline {
  fill: none;
  stroke: var(--app-acento);
  stroke-width: 1.5;
  stroke-linejoin: round;
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;   /* que el viewBox estirado no engorde el trazo */
}
.boca-serie-cifras {
  display: flex;
  justify-content: space-between;
  gap: .5rem;
  font-size: var(--fs-ayuda);
  font-variant-numeric: tabular-nums;
}

/* La mezcla, como dos barras apiladas enfrentadas. Cuatro tonos del MISMO acento y no
   cuatro colores: los productos son partes de un todo ordenadas por tamaño, que es
   exactamente lo que una escala secuencial de un solo tono codifica. La identidad no
   depende del color: cada tramo lleva su nombre y su porcentaje en la leyenda. */
.boca-mezcla {
  display: grid;
  grid-template-columns: 6rem 1fr;
  align-items: center;
  gap: .5rem 1rem;
}
.boca-mezcla-rotulo { font-size: var(--fs-ayuda); color: var(--app-rotulo-texto); }
.boca-apilada {
  display: flex;
  height: 18px;
  border-radius: var(--r-chip);
  overflow: hidden;
  background: var(--app-borde-suave);
  gap: 2px;                            /* el aire de la superficie entre tramos */
}
.boca-tramo-1 { background: #0C6F9F; }
.boca-tramo-2 { background: #3F8FB8; }
.boca-tramo-3 { background: #74B0CF; }
.boca-tramo-4 { background: #A8CFE3; }
.boca-leyenda {
  display: flex;
  flex-wrap: wrap;
  gap: .25rem 1.25rem;
  margin-top: .75rem;
  font-size: var(--fs-ayuda);
}
.boca-leyenda i {
  display: inline-block;
  width: .75rem;
  height: .75rem;
  border-radius: 3px;
  vertical-align: -1px;
  margin-right: .375rem;
}

@media (max-width: 767.98px) {
  .boca-serie { grid-template-columns: 1fr; row-gap: .25rem; }
  .boca-mezcla { grid-template-columns: 1fr; }
}

/* Quién encabeza cada producto, debajo del puesto. Es una tercera línea en una celda
   que ya tiene dos, así que va en el tamaño más chico y en gris: se lee cuando se la
   busca y no compite con la participación, que es el número de la celda. */
.pos-lider {
  display: block;
  font-size: var(--fs-rotulo);
  color: var(--app-rotulo-texto);
  max-width: 12rem;
  margin-left: auto;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.pos-lider a { color: inherit; }
.pos-lider a:hover { color: var(--app-acento); }

/* --- El tablero de gerencia -------------------------------------------------
   La portada que junta lo que calculan siete pantallas. Todo lo de acá sale de
   los tokens de arriba: no hay un solo color ni un solo tamaño suelto, que es
   lo que evita que la portada se vaya despegando del resto de la app.

   Tres reglas del diseño, escritas en CSS:
   1. Un solo azul para lo nuestro (--app-senal) y gris para toda comparación.
   2. Rojo y verde SOLO en deltas con signo, nunca en una barra entera, salvo el
      divergente de Mercado, donde la dirección ya dice el signo y el color
      apenas refuerza.
   3. Todo número con tabular-nums, o las columnas de cifras bailan al cambiar
      de mes. */

.tab-cabecera {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 1rem;
  flex-wrap: wrap;
}
/* `stretch` y no `center`: así el ojo toma la altura del selector sin que nadie tenga
   que mantener dos alturas iguales a mano. Antes el botón venía con el alto de un
   `btn-sm` y quedaba más grande que la caja de al lado. */
.tab-controles { display: flex; align-items: stretch; gap: .5rem; }
.tab-ojo {
  display: inline-flex;
  align-items: center;
  /* Angosto y cuadrado: adentro hay un ícono, no una palabra. El de un botón normal lo
     dejaba el doble de ancho que alto. */
  --bs-btn-padding-x: .5rem;
  --bs-btn-padding-y: 0;
  --bs-btn-font-size: var(--fs-ui);
}

/* --- La cortina de los importes ---
   Un ojo que tapa la plata para poder mostrar el tablero en una reunión. Es una cortina
   de PANTALLA y no un permiso: los números siguen en el HTML. Lo que esconde de verdad
   es el módulo `resultados`, que no es delegable.

   Se difumina en vez de reemplazar por puntos: el ancho no cambia, así que nada se
   mueve al taparlo y destaparlo, y una mancha del largo del número se lee como «esto
   está tapado» y no como «esto no se pudo calcular». `user-select: none` evita
   copiarlo sin querer de un tirón de selección. */
.plata-oculta .plata {
  filter: blur(7px);
  user-select: none;
  /* El color se apaga además del desenfoque: una mancha verde o roja del tamaño justo
     todavía dice si ganó o perdió, y cuánto. */
  color: var(--app-texto-2) !important;
}
/* El ojo muestra el estado SIGUIENTE, que es lo que uno va a obtener si lo aprieta. */
.tab-ojo .bi-eye-slash { display: none; }
.plata-oculta .tab-ojo .bi-eye { display: none; }
.plata-oculta .tab-ojo .bi-eye-slash { display: inline-block; }
.plata-oculta .tab-ojo { color: var(--app-texto); background: var(--app-fondo); }
/* Una sola caja, no tres botones pegados. El mes de adentro no es un control: es el
   rótulo de dónde estás parado, y como botón deshabilitado se leía como uno roto. */
.tab-selector {
  display: inline-flex;
  align-items: center;
  gap: .25rem;
  border: 1px solid var(--app-borde);
  background: #fff;
  border-radius: var(--r-control);
  padding: .25rem .5rem;
  font-size: var(--fs-menor);
  white-space: nowrap;
}
.tab-selector > a {
  display: inline-flex;
  align-items: center;
  color: var(--app-texto-2);
  text-decoration: none;
  padding: .1rem .3rem;
  border-radius: var(--r-chip);
}
.tab-selector > a:hover { color: var(--app-texto); background: var(--app-fondo); }
.tab-selector .tab-mes {
  /* Ancho fijo para que las flechas no se muevan al pasar de "mayo" a "septiembre":
     un control que se corre bajo el dedo se falla al segundo clic. */
  min-width: 8.5rem;
  text-align: center;
  font-variant-numeric: tabular-nums;
  color: var(--app-texto);
  font-weight: 500;
}

.tab-titular {
  font-size: var(--fs-texto);
  font-weight: 600;
  line-height: 1.35;
  margin-bottom: .25rem;
}

/* --- Las dos grillas ---
   Flex con `flex-grow` y no una grilla de N columnas fijas. Con columnas fijas, cinco
   tiles en una grilla de seis dejaban un hueco a la derecha, y tres módulos en dos
   columnas dejaban al tercero solo con media pantalla vacía al lado. Y la cantidad de
   piezas no es fija: depende de los permisos de cada uno y de qué datos hay cargados,
   así que cualquier número de columnas elegido a mano iba a dejar un hueco en algún
   caso. Creciendo, la última fila reparte lo que sobra y no sobra nada.

   El `flex-basis` es lo que decide cuántas entran por fila; el `min-width` de los
   módulos evita que un gráfico quede de 200px cuando hay cuatro. */
.tab-grilla { display: flex; flex-wrap: wrap; gap: .75rem; }
.tab-grilla > * { display: flex; }
.tab-grilla > * > .card { width: 100%; }
/* 11rem y no 9,5: con la base chica, en una tablet de 768 entraban cuatro por fila y
   el quinto se estiraba solo a lo ancho de la pantalla —una caja enorme con un número
   chiquito adentro—. Con 11rem entran tres y quedan dos abajo repartiéndose la fila.
   En escritorio no cambia nada: los cinco siguen entrando en una sola. */
.tab-tiles > * { flex: 1 1 11rem; }
.tab-modulos > * { flex: 1 1 45%; min-width: min(20rem, 100%); }

/* --- Los seis tiles --- */
.tab-tile { padding: .875rem 1rem; }
.tab-rotulo {
  font-size: var(--fs-rotulo);
  text-transform: uppercase;
  letter-spacing: .06em;
  font-weight: 650;
  color: var(--app-rotulo-texto);
}
.tab-valor {
  font-size: var(--fs-cifra);
  font-weight: 700;
  letter-spacing: -.01em;
  font-variant-numeric: tabular-nums;
  margin: .25rem 0 .1rem;
  line-height: 1.15;
}
.tab-delta {
  font-size: var(--fs-ayuda);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.tab-delta.mejor { color: var(--app-exito); }
.tab-delta.peor  { color: var(--app-peligro); }
.tab-delta.neutro { color: var(--app-texto-2); font-weight: 500; }
/* El "vs. 2025" no es parte de la cifra: es la etiqueta que la vuelve legible, y
   con el mismo peso competía con el número. */
.tab-contra { font-weight: 400; color: var(--app-texto-2); }
.tab-contexto {
  font-size: var(--fs-rotulo);
  color: var(--app-texto-2);
  margin-top: .15rem;
}

/* --- Los módulos --- */
.tab-modulo { display: flex; flex-direction: column; gap: .6rem; }
.tab-modulo-cab {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: .5rem;
}
.tab-ver { font-size: var(--fs-ayuda); text-decoration: none; white-space: nowrap; }
.tab-frase {
  font-size: var(--fs-menor);
  font-weight: 600;
  line-height: 1.4;
  margin: 0;
}
.tab-pie {
  font-size: var(--fs-rotulo);
  color: var(--app-texto-2);
  margin: 0;
}

/* Listas de a lo sumo cuatro filas. Más que eso deja de ser un tablero y pasa a
   ser la pantalla de detalle, que está a un clic. */
/* Una columna en una tarjeta angosta, dos en una ancha. Cuando el módulo ocupa la fila
   entera —pasa cuando son impares— el rótulo y su número quedaban a mil píxeles de
   distancia y el ojo tenía que cruzar un desierto para juntarlos. Con `auto-fit` las
   cuatro filas se parten solas en dos y el recorrido se corta a la mitad, sin que la
   plantilla tenga que saber qué ancho le tocó. */
.tab-filas {
  display: grid;
  /* `min(24rem, 100%)` y no `24rem` a secas: con el mínimo duro, la columna se planta
     en 384px aunque la tarjeta mida 310 y el documento entero se va de ancho en el
     teléfono. Es el desborde clásico de `auto-fit` con `minmax`. */
  grid-template-columns: repeat(auto-fit, minmax(min(24rem, 100%), 1fr));
  gap: .35rem 2.5rem;
  font-size: var(--fs-menor);
}
.tab-filas > div {
  display: flex;
  justify-content: space-between;
  gap: .75rem;
  border-bottom: 1px solid var(--app-borde-suave, var(--app-borde));
  padding-bottom: .3rem;
}
.tab-n { font-variant-numeric: tabular-nums; font-weight: 600; white-space: nowrap; }
/* Un resultado SÍ lleva color en el número y no solo en un delta: acá el signo no es
   una variación contra otra cosa, es la noticia. Ganó o perdió. */
.tab-gana { color: var(--app-exito); }
.tab-pierde { color: var(--app-peligro); }
/* Cuánto le falta a ese negocio para su propio mejor mes. Va debajo del importe y en
   gris: es contexto del número, no otro número. */
/* La comparación contra el año pasado. Un paso más grande que el resto de la tarjeta
   porque es la conclusión, no un dato de apoyo. */
.tab-interanual {
  font-size: var(--fs-menor);
  font-weight: 500;
  color: var(--app-texto-2);
  margin-top: -.2rem;
}
.tab-interanual .tab-gana,
.tab-interanual .tab-pierde { font-weight: 700; }

.tab-mejor {
  display: block;
  font-size: var(--fs-rotulo);
  font-weight: 400;
  color: var(--app-texto-2);
  text-align: right;
}

/* --- El divergente de Mercado --- */
/* El bloque crece con la tarjeta. Emparejada con una más alta, `h-100` estira esta y
   antes todo el sobrante caía junto al pie: cinco barras finas arriba y un hueco
   abajo. Ahora las filas se reparten ese alto y las barras engordan con ellas.

   El `max-height` de la pista es lo que impide que se vuelvan cintas: pasado ese
   grosor la barra deja de leerse como una medida y empieza a leerse como un bloque de
   color. Lo que sobre después del tope queda como aire entre filas, que es donde no
   molesta. */
.tab-divergente { display: flex; flex-direction: column; gap: .3rem; flex: 1 1 auto; }
.tab-div-fila {
  display: grid;
  grid-template-columns: 5.5rem 1fr 3.5rem;
  align-items: center;
  gap: .5rem;
  font-size: var(--fs-ayuda);
  flex: 1 1 auto;
  min-height: 1.35rem;
}
.tab-div-nombre { color: var(--app-texto-2); }
.tab-div-pista {
  position: relative;
  display: block;
  align-self: stretch;
  height: auto;
  min-height: 11px;
  max-height: 24px;
  /* Aire propio para que la barra no toque el borde de la fila de al lado cuando la
     tarjeta está estirada y las filas quedan pegadas. */
  margin-block: 3px;
}
.tab-div-cero {
  position: absolute;
  left: 50%;
  top: -1px;
  bottom: -1px;
  width: 1px;
  background: var(--app-borde-fuerte, var(--app-borde));
}
.tab-div-barra { position: absolute; top: 0; bottom: 0; height: auto; }
/* Redondeado solo del lado del dato: el extremo pegado al cero tiene que quedar
   a ras, o la barra parece arrancar un poco después de donde arranca. */
.tab-div-barra.peor  { background: var(--app-peligro-suave); border-radius: 3px 0 0 3px; }
.tab-div-barra.mejor { background: var(--app-exito-suave);   border-radius: 0 3px 3px 0; }
.tab-div-cifra { text-align: right; font-variant-numeric: tabular-nums; font-weight: 600; }
.tab-div-cifra.peor  { color: var(--app-peligro); }
.tab-div-cifra.mejor { color: var(--app-exito); }

/* Las tablas de la ficha, de `md` para arriba.

   Con `w-auto` se encogían al contenido y quedaban apretadas contra el borde izquierdo
   de una tarjeta ancha, con medio ancho de pantalla vacío al lado. Al 100 % a secas
   pasa lo contrario: cuatro columnas cortas se dispersan y hay que recorrer el renglón
   entero para juntar un dato con el siguiente.

   El reparto: la primera columna -la que lleva el texto largo, el tema o el tipo- se
   queda con lo que sobra, y las demás se ajustan a su contenido y quedan agrupadas a la
   derecha. Así la tabla ocupa la tarjeta sin desparramar los datos.

   Va de 768px para arriba porque abajo manda `.tabla-movil`, que pasa cada fila a
   tarjeta y pone las celdas en `display:block`: ahí estos anchos no aplican. */
@media (min-width: 768px) {
  .tabla-ficha { width: 100%; }
  .tabla-ficha > thead > tr > th:not(:first-child),
  .tabla-ficha > tbody > tr > td:not(:first-child) {
    width: 1%;
    white-space: nowrap;
  }
}

/* Las barras divergentes de la FICHA, que no viven donde las de Mercado.

   Allá el bloque va en una tarjeta de altura fija y las filas se estiran para llenarla,
   que es para lo que `.tab-div-pista` tiene `align-self: stretch` y altura elástica.
   Acá la tarjeta crece con su contenido, así que ese mismo estiramiento hacía que cada
   barra tomara la altura de SU fila: «Fines de semana» no entra en los 5,5rem de la
   columna del rótulo, envuelve a dos líneas, y esa barra salía casi el doble de gruesa
   que las otras tres. Se veían de tamaños distintos sin que el dato lo justificara.

   Se arregla por los dos lados: la columna del rótulo entra en una línea y la pista
   tiene una altura fija, así el grosor deja de depender de cuánto mide el texto. */
.ficha-carga .tab-div-fila { grid-template-columns: 8rem 1fr 3.5rem; }
.ficha-carga .tab-div-nombre { white-space: nowrap; }
.ficha-carga .tab-div-pista {
  align-self: center;
  min-height: 12px;
  max-height: 12px;
}

.tab-tipica {
  display: flex;
  flex-direction: column;
  gap: .25rem;
  font-size: var(--fs-ayuda);
  color: var(--app-texto-2);
  border-top: 1px solid var(--app-borde);
  padding-top: .5rem;
}
.tab-tipica .tab-n { color: var(--app-texto); }
.tab-veces { color: var(--app-texto-2); }
/* El GNC lleva su variación al lado, y una variación con signo se pinta como cualquier
   delta del tablero. */
.tab-veces.mejor { color: var(--app-exito); font-weight: 600; }
.tab-veces.peor  { color: var(--app-peligro); font-weight: 600; }

/* --- Los dos gráficos --- */
.tab-svg { width: 100%; height: auto; display: block; overflow: visible; }
/* Los rótulos de los meses viven acá y no adentro del SVG: ahí el texto escala con el
   dibujo y un font-size 10 sobre un viewBox de 300 estirado a 570px se dibuja a 19px.
   La grilla de N columnas iguales los alinea con los pares de barras. */
.tab-meses {
  display: grid;
  grid-template-columns: repeat(var(--tab-meses, 8), 1fr);
  font-size: var(--fs-rotulo);
  color: var(--app-texto-2);
  text-align: center;
  margin-top: .15rem;
}
.tab-leyenda {
  display: flex;
  gap: .75rem;
  font-size: var(--fs-rotulo);
  color: var(--app-texto-2);
  align-items: center;
}
.tab-leyenda i {
  display: inline-block;
  width: 10px;
  height: 10px;
  border-radius: 2px;
  vertical-align: -1px;
  margin-right: .25rem;
}

.tab-barras { display: flex; flex-direction: column; gap: .35rem; }
.tab-barra-fila {
  display: grid;
  grid-template-columns: 2.5rem 1fr 5.5rem;
  align-items: center;
  gap: .5rem;
  font-size: var(--fs-ayuda);
}
.tab-barra-mes { color: var(--app-texto-2); text-transform: capitalize; }
.tab-barra-pista { display: block; height: 10px; }
.tab-barra {
  display: block;
  height: 10px;
  border-radius: 0 3px 3px 0;
  background: var(--app-borde);
}
.tab-barra.actual { background: var(--app-senal); }
.tab-barra-cifra { text-align: right; font-variant-numeric: tabular-nums; color: var(--app-texto-2); }
.tab-barra-cifra.actual { color: var(--app-texto); font-weight: 600; }

/* --- La cascada del resultado ---
   Dos columnas: lo que entra y lo que sale. La tarjeta ocupa el ancho completo del
   tablero, y en una sola tira vertical dejaba media pantalla vacía a la derecha.

   Cada columna es una grilla con las mismas cuatro pistas, así los importes de las
   barras, el total del aporte y los renglones de estructura caen todos en la misma
   columna. Con un `space-between` en unos y una grilla en otros, los números quedaban
   corridos entre sí: era el desalineado que se veía.

   `auto-fit` + `min(…, 100%)` para partir en dos según el ancho REAL de la tarjeta y no
   del viewport, y sin el mínimo duro que desborda el teléfono. Es el mismo idioma que
   `.tab-filas`.

   El mínimo es 31rem y no el ancho que entre: las pistas fijas de cada fila —rótulo,
   importe y porción— se comen 20rem, así que en una columna más angosta la barra queda
   con lo que sobra. Medido: con la tarjeta en 780px se partía igual en dos y la barra
   quedaba en 33px, o sea que los cuatro negocios se veían del mismo largo y el gráfico
   dejaba de decir nada. Ese ancho es el de una laptop con el menú abierto, no un caso
   raro. Con 31rem se queda en una columna hasta que la barra tenga al menos 176px. */
.tab-cascada {
  margin-top: 1rem;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(31rem, 100%), 1fr));
  gap: 1.5rem 2.5rem;
}
.tab-casc-col { display: flex; flex-direction: column; gap: .35rem; }

/* Rótulo | barra | importe | porción. Las filas sin barra dejan la pista vacía en vez
   de correrse: es lo que mantiene los importes en una sola columna. */
.tab-casc-fila {
  display: grid;
  grid-template-columns: 9rem 1fr 7rem 2.5rem;
  align-items: center;
  gap: .5rem;
  row-gap: 0;
  font-size: var(--fs-ayuda);
}
.tab-casc-eti { line-height: 1.15; }
/* Las filas sin barra: el rótulo se come la pista vacía y el importe cae igual en la
   columna de números. Sin esto el importe se plantaba en la pista del medio y quedaba
   corrido respecto de los de las barras — es el desalineado que se veía. */
.tab-casc-plana .tab-casc-eti { grid-column: 1 / 3; }
.tab-casc-fila .tab-n { text-align: right; font-size: var(--fs-menor); }
.tab-casc-pct {
  text-align: right;
  font-variant-numeric: tabular-nums;
  color: var(--app-texto-2);
}
/* El «vs. su mejor mes» cruza desde la pista hasta el borde: es contexto del importe,
   no otro número, y en su propia línea de grilla no corre a los de arriba. */
.tab-casc-mejor {
  grid-column: 3 / -1;
  text-align: right;
  font-size: var(--fs-rotulo);
  color: var(--app-texto-2);
  margin-top: -.1rem;
}

.tab-casc-cab {
  margin-bottom: .25rem;
  font-size: var(--fs-rotulo);
}
.tab-casc-rot {
  grid-column: 1 / 3;
  text-transform: uppercase;
  letter-spacing: .07em;
  font-weight: 700;
  color: var(--app-rotulo-texto);
}
.tab-casc-cab .tab-n { color: var(--app-texto); }

/* La raya de arriba es la del cierre de una suma: lo de abajo es el total de lo de
   arriba, y eso se escribe con una línea, no con negrita. */
.tab-casc-total {
  margin-top: .35rem;
  padding-top: .5rem;
  border-top: 2px solid var(--app-borde);
  font-weight: 600;
}
/* Un negocio en pérdida: la barra mide su tamaño y el color dice el signo. */
.tab-barra.mala { background: var(--app-peligro); }

/* La tarjeta más alta del tablero ocupa la fila entera. Emparejada con otra, la otra se
   estiraba hasta su altura (`h-100`) y quedaba con media tarjeta en blanco abajo. */
.tab-modulos > .tab-ancho { flex-basis: 100%; }

/* --- El corte del reloj ---
   La separación la hace el espacio, no una letra chica: en una reunión, lo que
   está pegado se lee como si fuera lo mismo. */
.tab-corte {
  display: flex;
  align-items: baseline;
  gap: .5rem;
  margin: 2.25rem 0 .75rem;
  padding-top: 1rem;
  border-top: 1px solid var(--app-borde);
}
.tab-corte > span {
  font-size: var(--fs-rotulo);
  text-transform: uppercase;
  letter-spacing: .07em;
  font-weight: 700;
  color: var(--app-rotulo-texto);
}
.tab-corte > small { font-size: var(--fs-rotulo); color: var(--app-texto-2); }

@media (max-width: 575.98px) {
  /* En el teléfono la cifra baja un paso: a 24px, "2.942.854" se sale de una
     columna de media pantalla. */
  .tab-valor { font-size: var(--fs-cifra-sm); }
  .tab-div-fila { grid-template-columns: 4.5rem 1fr 3rem; font-size: var(--fs-rotulo); }
  .tab-barra-fila { grid-template-columns: 2.2rem 1fr 4.5rem; font-size: var(--fs-rotulo); }
  /* La fila pierde la columna de la porción: con 390px, cuatro pistas parten el importe
     en dos renglones y el número deja de leerse de un vistazo. La barra ya dice la
     proporción, que es para lo que estaba el porcentaje. */
  .tab-casc-fila { grid-template-columns: 6rem 1fr 6rem; font-size: var(--fs-rotulo); }
  .tab-casc-pct { display: none; }
  .tab-casc-mejor { grid-column: 2 / -1; }
}


/* El menú de gestión como panel en el teléfono y la tablet (ver _base_admin.html). De
   `xl` para arriba, `offcanvas-xl` no dibuja nada propio y estas reglas no aplican.

   El corte es 1199.98 y no 991.98 porque el iPad de 13" en vertical mide 1024: con el
   corte viejo caía del lado del sidebar fijo y la etiqueta se quedaba con 104px. */
@media (max-width: 1199.98px) {
  /* Su propia variable y no `width`: Bootstrap la escribe con la misma
     especificidad que cualquier clase, así que pelear por `width` es cuestión de
     quién carga después. Y 17rem en vez de los 400px de fábrica para que se vea un
     borde de la página atrás: eso es lo que dice "esto es un panel sobre lo que
     estabas mirando" y no "cambiaste de pantalla". */
  .sidebar-panel { --bs-offcanvas-width: 17rem; }
  .sidebar-panel .offcanvas-body { padding: .5rem .75rem 1rem; }
  .sidebar-panel .offcanvas-header { padding: .75rem .75rem .25rem; }
  .sidebar-panel-titulo {
    font-size: var(--fs-rotulo);
    text-transform: uppercase;
    letter-spacing: .07em;
    font-weight: 700;
    color: var(--app-rotulo-texto);
  }
}
