/* ============================================================
   AppsHub UAI — Componentes
   Tarjetas, enlaces, avisos, chips e insignias.

   Un patrón que se usa en dos pantallas sube acá desde style.css.
   Que el style.css de PAVE haya llegado a 127 KB es la señal de
   qué pasa cuando eso no se hace.
   ============================================================ */

/* ── Íconos ──────────────────────────────────────────────────
   Sprite SVG propio en lugar del webfont de Tabler por CDN: 17 KB
   contra 400, sin dominios externos en la CSP y sin que los
   íconos desaparezcan en silencio el día que el CDN no responda.

   Se dimensionan en `em` para que hereden el font-size de su
   contenedor, igual que hacía el webfont. */
.ti {
    width: 1em;
    height: 1em;
    flex: none;
    pointer-events: none;
}

/* ── Marca ───────────────────────────────────────────────────
   Color propio y no `--accent`: el acento se aclara en oscuro y
   en alto contraste es amarillo, y sobre amarillo el logo —blanco
   puro con transparencia— quedaba casi invisible. */
.marca-simbolo {
    --violeta-marca: #593597;
    width: 52px;
    height: 52px;
    border-radius: 14px;
    flex: none;
    background: var(--violeta-marca);
    color: #fff;
    border: 1px solid transparent;
    box-shadow: 0 3px 10px rgba(89, 53, 151, .28);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 29px;
}

.marca-simbolo img {
    width: 38px;
    height: 38px;
    object-fit: contain;
}

[data-theme="dark"] .marca-simbolo {
    --violeta-marca: #6d44b8;
    box-shadow: 0 3px 10px rgba(0, 0, 0, .5);
}

[data-theme="contrast"] .marca-simbolo {
    --violeta-marca: #000000;
    border-color: #ffff01;
    box-shadow: none;
}

@media (prefers-color-scheme: dark) {
    html:not([data-theme="light"]):not([data-theme="contrast"]) .marca-simbolo {
        --violeta-marca: #6d44b8;
    }
}

/* Cuadrado de esquinas redondeadas y no un círculo: comparte fila con el
   engranaje y con «Salir», y el mismo `--radius-lg` de ellos hace que los tres
   se lean como una sola botonera en vez de dos formas distintas pegadas. */
.avatar {
    position: relative;
    overflow: hidden;
    width: var(--alto-control);
    height: var(--alto-control);
    flex: none;
    border-radius: var(--radius-lg);
    background: var(--accent-bg);
    color: var(--accent-fg);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: var(--text-base);
    font-weight: var(--weight-bold);
    /* Es un `<button>` —abre «Mi perfil»— y estas tres líneas son las que el
       reset no toca: normaliza la tipografía y el fondo, pero el borde y el
       relleno del user-agent quedan, y con ellos la caja dejaba de medir los
       34px que comparte con el engranaje y «Salir». */
    border: 0;
    padding: 0;
    cursor: pointer;
}

.avatar:hover {
    filter: brightness(1.06);
}

/* El foco visible sí o sí: es el único control del encabezado que no tiene
   texto al lado, así que sin anillo no hay forma de saber que está enfocado. */
.avatar:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

/* La foto va ENCIMA de las iniciales, que siguen dibujadas debajo. Si no carga
   —un 404 porque esa persona no tiene, o Graph que nunca respondió— queda lo de
   siempre en vez de un hueco. `alt` vacío es lo que evita además el ícono de
   imagen rota: es decorativa, el nombre ya está al lado. */
.avatar img {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.saludo {
    font-size: var(--text-md);
    color: var(--text-muted);
}

/* ── Contador de sección ─────────────────────────────────── */

.contador {
    font-size: var(--text-sm);
    font-weight: var(--weight-bold);
    color: var(--accent-fg);
    background: var(--accent-bg);
    border-radius: var(--radius-full);
    white-space: nowrap;
    padding: 2px 9px;
}

/* ── Buscador ────────────────────────────────────────────── */

.buscador {
    position: relative;
    max-width: var(--ancho-buscador);
}

.buscador .ti {
    position: absolute;
    left: 13px;
    top: 50%;
    transform: translateY(-50%);
    color: var(--text-muted);
    font-size: 18px;
    pointer-events: none;
}

.buscador input {
    width: 100%;
    height: 42px;
    padding: 0 14px 0 42px;
    font-size: var(--text-md);
    color: var(--text-primary);
    background: var(--surface-card);
    border: 1px solid var(--border-default);
    border-radius: var(--radius-xl);
    outline: none;
    transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
}

.buscador input:focus {
    border-color: var(--accent);
    box-shadow: 0 0 0 3px var(--accent-bg);
}

/* Pista del atajo de teclado. `aria-hidden` y no texto real: quien navega con
   lector ya tiene el campo anunciado, y «Ctrl K» leído en medio del formulario
   sería ruido. Desaparece al enfocar o al escribir para no encimarse. */
.buscador:has(.atajo) input {
    padding-right: 64px;
}

.buscador .atajo {
    position: absolute;
    right: 10px;
    top: 50%;
    transform: translateY(-50%);
    font-size: 10.5px;
    font-weight: var(--weight-semibold);
    letter-spacing: .3px;
    color: var(--text-muted);
    background: var(--bg);
    border: 1px solid var(--border-default);
    border-radius: var(--radius-md);
    padding: 3px 7px;
    pointer-events: none;
    transition: opacity var(--transition-fast);
}

.buscador input:focus ~ .atajo,
.buscador input:not(:placeholder-shown) ~ .atajo {
    opacity: 0;
}

/* Con texto escrito el chip ya no está, pero su padding sí: la cruz nativa
   del `input search` quedaba flotando a 64px del borde, en el hueco que el
   «Ctrl K» reservaba. Va después de la regla del chip a propósito: misma
   especificidad, gana la última. */
.buscador input:not(:placeholder-shown) {
    padding-right: 14px;
}

/* ── El buscador propio de un módulo ─────────────────────────
   El de la biblioteca y el de horarios: un formulario con su botón,
   por `?q=` y con recarga, para que funcione sin JavaScript y una
   búsqueda se comparta por su URL. Nació en `biblioteca.css` y se
   mudó acá cuando horarios necesitó el mismo: con una copia en cada
   módulo, la segunda se queda con la versión vieja el día que se
   toque la primera. */
.buscador-modulo {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    max-width: var(--ancho-buscador);
    margin: 0 0 var(--space-6);
    padding: 0 var(--space-3);
    border: 1px solid var(--border-default);
    border-radius: var(--radius-lg);
    background: var(--surface-card);
}

.buscador-modulo .ti {
    flex: none;
    width: 18px;
    height: 18px;
    color: var(--text-muted);
}

.buscador-modulo input {
    flex: 1 1 auto;
    min-width: 0;
    height: 42px;
    border: 0;
    background: transparent;
    color: var(--text-primary);
    font: inherit;
}

.buscador-modulo input:focus {
    outline: none;
}

.buscador-modulo:focus-within {
    border-color: var(--accent);
}

/* ── La persona en una tabla de gestión ──────────────────────
   Foto de 30px —o sus iniciales— y el texto al lado. Existe para
   que una tabla de doscientas filas se recorra con el ojo: buscar
   a alguien por su cara es más rápido que leer doscientos nombres,
   y en las sedes mucha gente se conoce de vista antes que por su
   cuenta de correo.

   El círculo se dibuja igual sin foto, con las iniciales: una
   columna donde unas filas tienen imagen y otras nada salta al
   scrollear. */
.celda-persona {
    white-space: nowrap;
}

.avatar-mini {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 30px;
    height: 30px;
    flex: none;
    overflow: hidden;
    margin-right: var(--space-2);
    vertical-align: middle;
    border-radius: var(--radius-full);
    background: var(--accent-bg);
    color: var(--accent-fg);
    font-size: 11px;
    font-weight: var(--weight-bold);
    /* Las iniciales son texto, no una etiqueta: sin esto heredan el
       `letter-spacing` de la celda y se descentran. */
    letter-spacing: 0;
    /* Dos letras en un círculo de 30px: partidas dejan de ser iniciales. Es la
       misma regla que el resto de las pastillas, y hay un test que la exige. */
    white-space: nowrap;
}

.avatar-mini img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

/* El texto vuelve a poder envolver: sólo el bloque foto+nombre se
   mantiene junto, no el contenido entero de la celda. */
.persona-texto {
    display: inline-block;
    vertical-align: middle;
    white-space: normal;
}

/* ── Tarjeta ─────────────────────────────────────────────── */

.tarjeta {
    position: relative;
    display: flex;
    flex-direction: column;
    height: 100%;
    background: var(--surface-card);
    border: 1px solid var(--border-default);
    border-radius: var(--radius-xl);
    padding: 18px;
    text-decoration: none;
    color: inherit;
    cursor: pointer;
    transition: transform .15s ease, box-shadow .15s ease, border-color .15s ease;
}

.tarjeta:hover {
    transform: translateY(-3px);
    box-shadow: var(--shadow-md);
    border-color: var(--accent);
}

.tarjeta:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

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

/* El `padding-right` es el lugar de la estrella, que flota sobre la esquina.

   Se reserva SIEMPRE, no sólo cuando hay etiqueta de área: sin él, en pantallas
   angostas un título largo llegaba hasta debajo de la estrella. Y no cuesta
   nada — medido en 320, 375 y 1280px, ninguna tarjeta crece un píxel—, así que
   tampoco hace falta una media query que alguien tenga que mantener.

   Tampoco se reserva sólo en hover: eso cambiaría la altura de la tarjeta al
   pasar por encima y la mampostería recalcularía el layout debajo del mouse. */
.tarjeta-arriba {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    margin-bottom: var(--space-3);
    padding-right: var(--space-5);
}

.icono {
    position: relative;
    width: 46px;
    height: 46px;
    border-radius: 11px;
    flex: none;
    background: var(--accent-bg);
    color: var(--accent-fg);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 23px;
}

/* `min-width: 0` porque por omisión un hijo de flex no se encoge por debajo del
   ancho de su contenido, y con la etiqueta de área en la misma fila eso hacía
   que un nombre largo la empujara fuera del padding, hasta debajo de la
   estrella. Pasaba sólo en pantallas angostas, que es donde nadie mira. */
.tarjeta-titulo {
    min-width: 0;
    overflow-wrap: break-word;
    font-size: var(--text-lg);
    font-weight: var(--weight-semibold);
    line-height: 1.25;
}

.tarjeta-desc {
    margin: 0 0 var(--space-4);
    font-size: var(--text-base);
    color: var(--text-muted);
    line-height: 1.5;
    flex: 1;
}

/* Quién responde las dudas de uso de esa app. Deliberadamente discreto: no
   compite con el título ni con la acción, y quien no lo necesita no lo mira.
   Usa `--text-muted` y no un gris propio para que el tema de alta resolución,
   que lo lleva a blanco puro, también lo alcance. */
.tarjeta-soporte {
    margin: 0 0 var(--space-3);
    font-size: var(--text-xs);
    color: var(--text-muted);
    line-height: 1.4;
    /* El correo es una sola palabra larga y sin esto desborda la tarjeta. */
    overflow-wrap: anywhere;
}

.accion-abrir {
    margin-top: auto;
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    font-size: var(--text-base);
    font-weight: var(--weight-semibold);
    color: var(--accent);
}

.accion-abrir .ti {
    font-size: var(--text-lg);
}

/* ── Tarjeta bloqueada ───────────────────────────────────────
   **No navega.** Antes conservaba su URL —el argumento era que cada sistema
   aplica su propia autorización— y en la práctica se leía como un error del
   hub: candado puesto, tarjeta que abre igual. Ahora el clic explica por qué y
   a quién pedirle acceso.

   `not-allowed` es la consecuencia de eso en el cursor. `pointer` significa una
   cosa concreta en la web —esto te lleva a algún lado— y acá no lleva: era la
   única señal de la tarjeta que seguía diciendo lo contrario que las otras
   tres, el candado, la opacidad y el «Sin acceso».

   No es un cursor propio con un candado dibujado: `not-allowed` se ve nítido en
   cualquier densidad de pantalla, no pasa por la CSP como imagen y significa
   esto mismo en todos los sistemas. Que igual se pueda hacer clic para leer el
   motivo no lo contradice — lo que no está permitido es abrir el sistema, que
   es lo que la tarjeta promete. */
.tarjeta.bloqueada,
.enlace.bloqueada {
    opacity: .6;
    filter: grayscale(.45);
    cursor: not-allowed;
}

.tarjeta.bloqueada:hover,
.enlace.bloqueada:hover {
    transform: none;
    box-shadow: none;
    border-color: var(--border-default);
}

.accion-bloqueada {
    margin-top: auto;
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    font-size: var(--text-base);
    font-weight: var(--weight-semibold);
    color: var(--text-muted);
}

.accion-bloqueada .ti {
    font-size: var(--text-lg);
}

/* «Solicitar» va en la misma línea que «Sin acceso», que es donde la mirada
   termina de leer el estado: la pregunta que sigue —«¿y a quién se lo pido?»—
   se responde ahí mismo. */
.boton-solicitar {
    font: inherit;
    font-size: var(--text-sm);
    font-weight: var(--weight-bold);
    color: var(--accent-fg);
    background: var(--accent-bg);
    border: 1px solid transparent;
    border-radius: 7px;
    padding: 3px 10px;
    margin-left: var(--space-2);
    cursor: pointer;
    transition: filter var(--transition-fast);
}

.boton-solicitar:hover {
    filter: brightness(.96);
}

[data-theme="contrast"] .boton-solicitar {
    border-color: #ffff01;
}

/* La tarjeta bloqueada se apaga con opacity y grayscale en el contenedor, y eso
   arrastraría también al botón: compuesto sobre el blanco de la tarjeta queda en
   2.6:1, ilegible, y `opacity` no se puede revertir en un hijo.

   Con un botón adentro, entonces, la atenuación baja del contenedor a sus
   partes y el botón queda fuera. «Sin acceso» no necesita atenuarse: ya usa el
   gris de texto secundario. */
.tarjeta-envoltorio:has(.boton-solicitar) .tarjeta.bloqueada {
    opacity: 1;
    filter: none;
}

.tarjeta-envoltorio:has(.boton-solicitar) .tarjeta.bloqueada .tarjeta-arriba,
.tarjeta-envoltorio:has(.boton-solicitar) .tarjeta.bloqueada .tarjeta-desc,
.tarjeta-envoltorio:has(.boton-solicitar) .tarjeta.bloqueada .nota,
.tarjeta-envoltorio:has(.boton-solicitar) .tarjeta.bloqueada .etiqueta-area,
.tarjeta-envoltorio:has(.boton-solicitar) .tarjeta.bloqueada .accion-bloqueada > .ti {
    opacity: .6;
    filter: grayscale(.45);
}

/* ── Enlace (tile de la sección adicional) ───────────────── */

.enlace {
    display: flex;
    align-items: flex-start;
    gap: var(--space-3);
    padding: var(--space-3) 13px;
    background: var(--surface-card);
    border: 1px solid var(--border-default);
    border-radius: var(--radius-lg);
    text-decoration: none;
    color: inherit;
    cursor: pointer;
    transition: transform var(--transition-fast), box-shadow var(--transition-fast), border-color var(--transition-fast);
}

.enlace:hover {
    transform: translateY(-1px);
    box-shadow: var(--shadow-md);
    border-color: var(--accent);
}

.enlace:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

.enlace .icono {
    width: 36px;
    height: 36px;
    border-radius: 9px;
    font-size: 18px;
}

/* `min-width: 0` para que el texto largo rompa en vez de desbordar: un hijo de
   flex no encoge por debajo del ancho de su contenido sin esto. */
.enlace-texto {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
}

.enlace-titulo {
    font-size: var(--text-md);
    font-weight: var(--weight-semibold);
    line-height: 1.25;
}

.enlace-desc {
    font-size: var(--text-sm);
    color: var(--text-muted);
    line-height: 1.4;
}

.enlace-flecha {
    margin-left: auto;
    color: var(--text-muted);
    font-size: var(--text-lg);
    flex: none;
}

/* ── «Mis accesos» ───────────────────────────────────────────
   El botón no puede vivir dentro de la tarjeta —un <button> anidado en un <a>
   es HTML inválido— así que cada tarjeta va envuelta y el botón es su hermano.
   El envoltorio pasa a ser el hijo directo de la grilla, y por eso el margen de
   la mampostería apunta a `> *`. */

.tarjeta-envoltorio {
    position: relative;
}

.favorito {
    position: absolute;
    top: 9px;
    right: 9px;
    z-index: 2;
}

.boton-favorito {
    width: 28px;
    height: 28px;
    padding: 0;
    border-radius: var(--radius-lg);
    display: flex;
    align-items: center;
    justify-content: center;
    font: inherit;
    font-size: 15px;
    color: var(--text-muted);
    background: var(--surface-card);
    border: 1px solid var(--border-default);
    cursor: pointer;
    transition: opacity var(--transition-fast), color var(--transition-fast),
        border-color var(--transition-fast), transform .15s ease;
}

.boton-favorito:hover {
    color: var(--accent);
    border-color: var(--accent);
}

.boton-favorito:focus-visible {
    opacity: 1;
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

/* Marcada toma el acento de su sección y no el dorado de costumbre: el ámbar en
   este hub ya significa Power BI. */
.boton-favorito.marcado {
    color: var(--accent-fg);
    background: var(--accent-bg);
    border-color: var(--accent);
}

/* Con mouse la estrella aparece al pasar por encima: treinta estrellas fijas
   competirían con el contenido de las tarjetas. Ya marcada queda siempre a la
   vista. En pantallas táctiles no hay hover, así que ahí se muestran todas. */
@media (hover: hover) {
    .boton-favorito:not(.marcado) {
        opacity: 0;
    }

    .tarjeta-envoltorio:hover .boton-favorito,
    .tarjeta-envoltorio:focus-within .boton-favorito {
        opacity: 1;
    }
}

/* El hover se pasa al envoltorio. Si quedara en la tarjeta, acercar el mouse a
   la estrella —que es hermana y está por encima— la sacaría del hover y la
   tarjeta bajaría de golpe justo al ir a marcarla. El botón acompaña el
   desplazamiento o quedaría flotando tres píxeles más abajo. */
/* `--realce` y no `var(--bi)`.
   
   Ese fallback venía del hub estático, donde `--bi` se definía DENTRO de la
   sección de Power BI y por eso el respaldo funcionaba. Acá vive en `:root`,
   así que está siempre definida y el `var(--accent)` **nunca se usaba**: todas
   las tarjetas del hub se realzaban en ámbar al pasar por encima, incluidas las
   de los módulos y las de los enlaces.
   
   No daba ningún error y el código se leía como si anduviera, que es la peor
   combinación. Ahora cada acento declara su realce y la regla lee esa variable
   —sin fallback, porque un fallback que no se puede probar es una mentira que
   espera. */
.tarjeta-envoltorio:hover .tarjeta:not(.bloqueada) {
    transform: translateY(-3px);
    box-shadow: var(--shadow-md);
    border-color: var(--realce);
}

.tarjeta-envoltorio:hover .enlace:not(.bloqueada) {
    transform: translateY(-1px);
    box-shadow: var(--shadow-md);
    border-color: var(--accent);
}

.tarjeta-envoltorio:hover .boton-favorito {
    transform: translateY(-3px);
}

.tarjeta-envoltorio:has(.enlace):hover .boton-favorito {
    transform: translateY(-1px);
}

/* Sobre una bloqueada la estrella se levantaba sola mientras la tarjeta se
   quedaba quieta. */
.tarjeta-envoltorio:has(.bloqueada):hover .boton-favorito {
    transform: none;
}

/* En el tile la estrella no se hace lugar: ocupa el de la flecha, que se
   desvanece. Reservarle espacio con padding le comía ancho al texto, que pasaba
   a romper en más renglones. Y como todo el hub abre en pestaña nueva, esa
   flecha no distingue nada. */
.enlace-flecha {
    transition: opacity var(--transition-fast);
}

/* **Sólo la flecha del enlace que abre.**

   En un tile adicional el candado y la flecha son el mismo span, que cambia de
   símbolo según el estado. Sin el `:not(.bloqueada)`, estas reglas se llevaban
   puesto el único indicio visible de que algo está bloqueado: desaparecía al
   pasar por encima, al enfocar, al fijar la tarjeta, y —por el bloque de
   abajo— **siempre en pantallas táctiles**, donde no hay hover que lo devuelva.

   Se veía raro en la misma pantalla: una tarjeta adicional fijada aparece dos
   veces, con candado arriba en «Mis accesos» —que usa la otra macro— y sin nada
   abajo. */
.tarjeta-envoltorio:hover .enlace:not(.bloqueada) .enlace-flecha,
.tarjeta-envoltorio:focus-within .enlace:not(.bloqueada) .enlace-flecha,
.tarjeta-envoltorio:has(.boton-favorito.marcado) .enlace:not(.bloqueada) .enlace-flecha {
    opacity: 0;
}

@media (hover: none) {
    .tarjeta-envoltorio .enlace:not(.bloqueada) .enlace-flecha {
        opacity: 0;
    }
}

/* Tarjeta compacta de «Mis accesos»: ícono y nombre. El margen inferior de la
   fila superior desaparece porque ya no tiene nada debajo que separar. */
.tarjeta.compacta {
    padding: 14px;
}

.tarjeta.compacta .tarjeta-arriba {
    margin-bottom: 0;
}

.tarjeta.compacta .accion-bloqueada {
    margin-top: var(--space-3);
}

/* ── Insignias dentro de la tarjeta ──────────────────────── */

/* Destacado: sobre el ícono y no en la esquina, que ya la ocupa la etiqueta de
   área — en las vistas que mezclan áreas las dos aparecen a la vez.

   Corazón y no estrella: la estrella es el botón de «Mis accesos», y dos
   estrellas con significados distintos en la misma tarjeta —una decidida por la
   institución, otra por la persona— se leen como la misma cosa. */
.marca-destacado {
    position: absolute;
    top: -5px;
    right: -5px;
    width: 19px;
    height: 19px;
    border-radius: 50%;
    background: var(--surface-card);
    color: var(--bi-fg);
    border: 1px solid var(--border-default);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 11px;
}

/* Etiqueta de área. Sólo la usan los reportes.

   **En el flujo de la fila del título, no flotando sobre la tarjeta.** Estaba
   en `position:absolute`, y así el único modo de que no tapara el nombre era
   reservarle espacio con un `padding-right` a ojo. Ese número no podía existir:
   el ancho de la etiqueta depende del área, que se escribe desde `/gestion`.
   Con «Investigación» hacían falta 155px y había 82 — doce de las trece
   tarjetas con área tapaban su propio título.

   `align-self` arriba y no centrada: con un nombre de dos renglones la etiqueta
   quedaba a media altura, lejos de la esquina donde se la busca. */
.etiqueta-area {
    flex: none;
    margin-left: auto;
    align-self: flex-start;
    margin-top: 2px;
    font-size: 10px;
    font-weight: var(--weight-bold);
    letter-spacing: .5px;
    text-transform: uppercase;
    color: var(--acento-texto);
    background: var(--bi-soft);
    border-radius: var(--radius-full);
    padding: 3px 9px;
    white-space: nowrap;
}

/* Alta reciente. Pegada al título y no en una esquina: las dos esquinas ya están
   tomadas —la etiqueta de área arriba a la derecha, el destacado sobre el
   ícono— y en la vista «Todos» aparecen a la vez. */
.insignia-nueva {
    display: inline-block;
    vertical-align: middle;
    margin-left: 7px;
    font-size: 9.5px;
    font-weight: var(--weight-bold);
    letter-spacing: .6px;
    text-transform: uppercase;
    color: var(--nuevo-fg);
    background: var(--nuevo-bg);
    border: 1px solid transparent;
    border-radius: var(--radius-full);
    white-space: nowrap;
    padding: 2px 7px;
}

[data-theme="contrast"] .insignia-nueva {
    border-color: #ffffff;
}

/* ── Nota dentro de la tarjeta ───────────────────────────────
   Franja entre la descripción y la acción. La estructura vive acá y el color lo
   pone la variante, que se elige de una tabla del servidor y nunca con un valor
   crudo del catálogo.

   El borde transparente existe siempre para que en alto contraste el bloque no
   se pierda sobre el fondo negro. */
.nota {
    display: flex;
    align-items: flex-start;
    gap: 7px;
    margin: 0 0 14px;
    padding: var(--space-2) 11px;
    font-size: var(--text-sm);
    font-weight: var(--weight-semibold);
    line-height: 1.45;
    color: var(--nota-fg);
    background: var(--nota-bg);
    border: 1px solid transparent;
    border-radius: 9px;
}

.nota .ti {
    font-size: var(--text-md);
    margin-top: 1px;
}

.nota-acceso {
    --nota-bg: var(--nota-acceso-bg);
    --nota-fg: var(--nota-acceso-fg);
}

.nota-dev {
    --nota-bg: var(--nota-dev-bg);
    --nota-fg: var(--nota-dev-fg);
}

[data-theme="contrast"] .nota-acceso {
    border-color: #ffff01;
}

[data-theme="contrast"] .nota-dev {
    border-color: #ffffff;
}

/* ── Franja de avisos ────────────────────────────────────── */

/* El margen va ABAJO, como en las secciones.

   La franja se inserta arriba del catálogo, así que lo que hay que separar es
   lo que viene después: con el margen arriba, el primer título de sección se le
   pegaba. Y el aire de arriba ya estaba puesto — el buscador cierra con 40px.

   34px es lo mismo que separa dos secciones: la franja es un bloque más del
   ritmo vertical, no una excepción. */
.avisos {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
    margin: 0 0 34px;
}

.aviso {
    display: flex;
    align-items: flex-start;
    gap: var(--space-3);
    padding: var(--space-3) 14px;
    font-size: var(--text-base);
    line-height: 1.5;
    color: var(--aviso-fg);
    background: var(--aviso-bg);
    border: 1px solid transparent;
    border-radius: var(--radius-lg);
}

.aviso > .ti {
    font-size: 17px;
    margin-top: 1px;
}

.aviso-texto {
    margin: 0;
    flex: 1;
    min-width: 0;
    font-weight: var(--weight-medium);
}

.aviso-enlace {
    color: inherit;
    font-weight: var(--weight-bold);
    text-decoration: underline;
    text-underline-offset: 2px;
}

/* .85 y no .6: atenuado sobre los fondos suaves de aviso el contraste caía a
   2.59:1 en «novedad» y 2.86:1 en «info», por debajo del 3:1 que WCAG 1.4.11
   pide para un control. */
.aviso-cerrar {
    font: inherit;
    color: inherit;
    background: transparent;
    border: 0;
    margin: -3px -4px 0 0;
    padding: var(--space-1);
    line-height: 0;
    border-radius: var(--radius-md);
    cursor: pointer;
    opacity: .85;
    flex: none;
    font-size: var(--text-lg);
    transition: opacity var(--transition-fast);
}

.aviso-cerrar:hover {
    opacity: 1;
}

.aviso-cerrar:focus-visible {
    opacity: 1;
    outline: 2px solid currentColor;
    outline-offset: 1px;
}

.aviso-mantenimiento {
    --aviso-bg: var(--aviso-mant-bg);
    --aviso-fg: var(--aviso-mant-fg);
}

.aviso-novedad {
    --aviso-bg: var(--aviso-nov-bg);
    --aviso-fg: var(--aviso-nov-fg);
}

.aviso-info {
    --aviso-bg: var(--aviso-info-bg);
    --aviso-fg: var(--aviso-info-fg);
}

[data-theme="contrast"] .aviso {
    border-color: #ffffff;
}

/* ── La portada: avisos arriba, agenda abajo ─────────────────
   Apiladas, las dos al ancho completo. Estuvieron en dos columnas
   y no funcionó: los avisos miden dos renglones y la agenda cinco,
   así que la izquierda quedaba con un hueco del alto de la derecha
   y el catálogo —que es a lo que se entra— arrancaba mucho más
   abajo de lo necesario.

   El `gap` en lugar de márgenes propios es lo que hace que, cuando
   el JavaScript borra la franja de avisos porque la persona los
   cerró todos, no quede ningún hueco: un elemento que no está no
   genera separación. */
.portada {
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
    margin: 0 0 34px;
}

/* Dentro de la portada la separación la pone el `gap`. El margen
   propio de la franja de avisos es para cuando va sola. */
.portada > .avisos {
    margin-bottom: 0;
}

/* ── La franja de agenda ─────────────────────────────────── */
.agenda-portada {
    padding: var(--space-3) var(--space-4) var(--space-4);
    background: var(--surface-card);
    border: 1px solid var(--border-default);
    border-radius: var(--radius-lg);
}

/* Título, barra del ciclo y «Ver todo» en un renglón. La barra se
   queda con lo que sobra, que es lo único elástico de los tres. */
.agenda-cabecera {
    display: flex;
    align-items: center;
    gap: var(--space-4);
    margin-bottom: var(--space-3);
}

.agenda-cabecera h2 {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    margin: 0;
    font-size: var(--text-md);
    font-weight: var(--weight-semibold);
    color: var(--text-primary);
    white-space: nowrap;
}

.agenda-cabecera h2 > .ti {
    color: var(--accent);
}

.agenda-vertodo {
    display: inline-flex;
    align-items: center;
    gap: 2px;
    margin-left: auto;
    font-size: var(--text-sm);
    font-weight: var(--weight-medium);
    color: var(--accent);
    text-decoration: none;
    white-space: nowrap;
}

.agenda-vertodo:hover {
    text-decoration: underline;
}

.agenda-vertodo > .ti {
    font-size: 14px;
}

/* ── La barra del ciclo ──────────────────────────────────────
   Es un `<progress>` y no un `<div>` con el ancho pintado: la CSP
   del hub no admite estilos embebidos, así que un `style` con el
   ancho no llega nunca al navegador y la barra sale vacía **sin
   ningún error**. El valor viaja en un atributo, que la CSP no
   toca, y de yapa el elemento ya es accesible sin ARIA.

   El precio es estilarlo tres veces: el estándar, el de WebKit y
   el de Firefox. No hay forma de unificarlos. */
.ciclo {
    display: flex;
    flex: 1 1 auto;
    align-items: center;
    gap: var(--space-2);
    min-width: 0;
    /* Pasado ese ancho la barra se estira sin decir nada más: el
       renglón no es una gráfica, es un dato al paso. */
    max-width: 520px;
}

.ciclo-etapa {
    font-size: var(--text-sm);
    font-weight: var(--weight-semibold);
    color: var(--text-secondary);
    white-space: nowrap;
}

.ciclo-porcentaje {
    font-size: var(--text-sm);
    font-weight: var(--weight-bold);
    color: var(--accent);
    font-variant-numeric: tabular-nums;
}

.ciclo-barra {
    -webkit-appearance: none;
    -moz-appearance: none;
    appearance: none;
    flex: 1 1 auto;
    min-width: 60px;
    height: 6px;
    border: 0;
    border-radius: 999px;
    overflow: hidden;
    /* Firefox pinta el riel con el `background` del elemento. */
    background: var(--surface-sunken);
}

.ciclo-barra::-webkit-progress-bar {
    background: var(--surface-sunken);
    border-radius: 999px;
}

.ciclo-barra::-webkit-progress-value {
    background: var(--accent);
    border-radius: 999px;
}

.ciclo-barra::-moz-progress-bar {
    background: var(--accent);
    border-radius: 999px;
}

.ciclo-pie {
    font-size: var(--text-xs);
    color: var(--text-muted);
    white-space: nowrap;
}

/* ── Las actividades ─────────────────────────────────────── */
.agenda-lista {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    margin: 0;
    padding: 0;
    list-style: none;
}

/* Las seis de la portada, en columnas que se acomodan solas: tres
   en escritorio, dos en tablet, una en el teléfono. El `auto-fit`
   evita fijar el número por punto de corte y volver a tocarlo cada
   vez que cambia el ancho del envoltorio. */
.agenda-columnas {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(255px, 1fr));
    gap: var(--space-2) var(--space-4);
}

.agenda-item {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    min-width: 0;
}

.agenda-fecha {
    display: flex;
    flex: 0 0 44px;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    padding: 5px 0;
    border-radius: var(--radius-md);
    /* El tipo redefine estas dos y nada más. */
    background: var(--agenda-bg, var(--agenda-cursada-bg));
    color: var(--agenda-fg, var(--agenda-cursada-fg));
}

.agenda-dia {
    font-size: var(--text-md);
    font-weight: var(--weight-bold);
    line-height: 1.1;
    font-variant-numeric: tabular-nums;
}

/* «05–12» no entra en el mismo cuerpo que «05». */
.agenda-dia-doble {
    font-size: var(--text-sm);
}

.agenda-mes {
    font-size: 9.5px;
    font-weight: var(--weight-semibold);
    letter-spacing: 0.06em;
    text-transform: uppercase;
}

.agenda-que {
    min-width: 0;
}

.agenda-titulo {
    margin: 0;
    font-size: var(--text-base);
    line-height: 1.35;
    color: var(--text-primary);
    /* Dos renglones y puntos suspensivos: los títulos del calendario
       llegan a «Inscripción a Centros Formadores de las PPS (5to año
       Psicología)», y sin tope una celda estira toda su fila. */
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.agenda-cuando {
    margin: 1px 0 0;
    font-size: var(--text-xs);
    color: var(--text-muted);
}

.agenda-ahora {
    font-weight: var(--weight-semibold);
    color: var(--agenda-fg, var(--accent));
}

.agenda-vacia {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    margin: 0;
    font-size: var(--text-base);
    color: var(--text-muted);
}

.agenda-ciclo {
    --agenda-bg: var(--agenda-ciclo-bg);
    --agenda-fg: var(--agenda-ciclo-fg);
}

.agenda-examen {
    --agenda-bg: var(--agenda-examen-bg);
    --agenda-fg: var(--agenda-examen-fg);
}

.agenda-inscripcion {
    --agenda-bg: var(--agenda-inscripcion-bg);
    --agenda-fg: var(--agenda-inscripcion-fg);
}

.agenda-receso {
    --agenda-bg: var(--agenda-receso-bg);
    --agenda-fg: var(--agenda-receso-fg);
}

.agenda-cursada {
    --agenda-bg: var(--agenda-cursada-bg);
    --agenda-fg: var(--agenda-cursada-fg);
}

.agenda-feriado {
    --agenda-bg: var(--agenda-feriado-bg);
    --agenda-fg: var(--agenda-feriado-fg);
}

[data-theme="contrast"] .agenda-fecha {
    border: 1px solid currentColor;
}

/* La cabecera en tres partes no entra en un renglón angosto: el
   título y «Ver todo» quedan arriba y la barra pasa abajo, entera. */
@media (max-width: 720px) {
    .agenda-cabecera {
        flex-wrap: wrap;
        gap: var(--space-2);
    }

    .ciclo {
        order: 3;
        flex-basis: 100%;
        max-width: none;
    }
}

/* En el teléfono la grilla queda en una columna, y seis renglones
   empujarían el catálogo fuera de la primera pantalla. Se muestran
   cuatro: el servidor manda las seis igual, así que al girar el
   equipo aparecen las otras dos sin volver a pedir nada. */
@media (max-width: 560px) {
    .agenda-columnas > :nth-child(n + 5) {
        display: none;
    }

    .ciclo-pie {
        display: none;
    }
}

/* ── La pantalla completa de `/agenda` ───────────────────────
   Las mismas piezas que la columna del hub, con dos agregados que
   ahí no hacen falta: el corte por mes y el gris de lo que ya
   pasó. #}
/* En la pantalla del año el mismo renglón va solo, en su caja y sin
   tope de ancho: no compite con un título ni con «Ver todo». */
.ciclo-ancho {
    max-width: none;
    padding: var(--space-3) var(--space-4);
    margin-bottom: var(--space-4);
    background: var(--surface-card);
    border: 1px solid var(--border-default);
    border-radius: var(--radius-lg);
}

.chips-agenda .chip[aria-current="page"] {
    color: var(--accent-fg);
    background: var(--accent-bg);
    border-color: var(--accent);
}

.chips-agenda .chip:hover {
    border-color: var(--accent);
}

.agenda-completa {
    gap: var(--space-1);
}

.agenda-completa .agenda-item {
    padding: var(--space-2);
    background: var(--surface-card);
    border: 1px solid var(--border-default);
    border-radius: var(--radius-lg);
}

.agenda-mes-titulo {
    margin: var(--space-4) 0 2px;
    font-size: var(--text-xs);
    font-weight: var(--weight-bold);
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--text-muted);
}

.agenda-mes-titulo:first-child {
    margin-top: 0;
}

/* Lo que ya pasó no se esconde —el calendario del año es también un
   registro de lo que hubo— pero se corre al fondo. */
.agenda-pasada {
    opacity: 0.55;
}

.agenda-completa .agenda-titulo {
    -webkit-line-clamp: 3;
}

/* ── Licencia de Power BI ────────────────────────────────── */

/* Un renglón entre el título de Reportes y los chips. Los dos estados pesan
   distinto a propósito: quien tiene licencia ya sabe lo que necesita saber y no
   hay que interrumpirlo, mientras que quien no figura tiene algo que hacer. */

/* Aire parejo arriba y abajo. La primera versión traía un margen superior
   negativo para pegar la leyenda al título, y el resultado eran dos píxeles de
   separación: el renglón se leía como parte del `<h2>` en vez de como una línea
   propia. Los 16 de arriba ganan sobre los 14 del título al colapsar. */
.licencia {
    display: flex;
    align-items: flex-start;
    gap: var(--space-2);
    margin: var(--space-4) 0;
    font-size: 13px;
    line-height: 1.45;
}

.licencia .ti {
    flex: none;
    /* Alineado con la primera línea y no con el bloque: cuando el texto se parte
       en dos renglones, `center` deja el ícono flotando en el medio. */
    margin-top: 1px;
}

.licencia-si {
    color: var(--text-muted);
}

/* Sólo el tilde en verde, no el texto: en verde entero el renglón compite con el
   título de la sección, y lo que hay que ver de un vistazo es que está bien —
   eso lo dice el ícono. El mismo verde que el aviso de novedad. */
.licencia-si .ti {
    color: var(--aviso-nov-fg);
}

/* Naranja, con los tokens que ya usa la nota de acceso de las tarjetas: es
   exactamente el mismo asunto —te falta un permiso para esto— y merece leerse
   igual. No es rojo porque no hay nada roto, y no es azul porque no es un dato
   más: acá hay algo que hacer. */
.licencia-no {
    padding: var(--space-2) var(--space-3);
    border-radius: var(--radius-md);
    color: var(--nota-acceso-fg);
    background: var(--nota-acceso-bg);
}

.licencia-no .ti {
    color: inherit;
}

/* En su propia línea: es la parte accionable y pegada al final del párrafo se
   pierde. */
.licencia-pedir {
    display: block;
    font-weight: var(--weight-semibold);
}

[data-theme="contrast"] .licencia-no {
    border: 1px solid #ffffff;
}

/* ── Chips de filtro ─────────────────────────────────────── */

.chips {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    margin: 0 0 var(--space-4);
}

.chip {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    font: inherit;
    font-size: 12.5px;
    font-weight: var(--weight-semibold);
    line-height: 1.2;
    color: var(--text-muted);
    background: var(--surface-card);
    border: 1px solid var(--border-default);
    border-radius: var(--radius-full);
    /* Una pastilla partida en dos renglones deja de ser una pastilla: el
       redondeo completo sobre una caja de dos líneas dibuja un rectángulo de
       esquinas muy redondeadas. */
    white-space: nowrap;
    /* Y el tope, que las otras pastillas no necesitan: el texto de este chip es
       el área de un reporte, que la escribe alguien en `/gestion/reportes`. Sin
       partirse y sin recortar, un área larga se llevaría la página entera hacia
       el costado en un teléfono. */
    max-width: 100%;
    overflow: hidden;
    padding: var(--space-1) 13px;
    cursor: pointer;
    transition: color var(--transition-fast), background-color var(--transition-fast), border-color var(--transition-fast);
}

.chip .ti {
    font-size: var(--text-md);
}

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

/* Las dos formas: los chips de reportes son `<button>` y usan `aria-pressed`,
   que es lo correcto para un interruptor; los de Personas son enlaces de verdad
   —cambian la URL— y ahí `aria-pressed` es ARIA inválido, así que llevan
   `aria-current`, igual que la barra de pestañas de gestión. */
.chip[aria-pressed="true"],
.chip[aria-current="page"] {
    color: var(--bi-fg);
    background: var(--bi-soft);
    border-color: var(--bi);
}

.chip:focus-visible {
    outline: 2px solid var(--bi);
    outline-offset: 2px;
}

.chip:active {
    transform: scale(.97);
}

/* ── Botón «ver más» ─────────────────────────────────────── */

.mostrar-mas {
    display: flex;
    justify-content: center;
    margin-top: 18px;
}

.boton-mas {
    font: inherit;
    font-size: var(--text-base);
    font-weight: var(--weight-semibold);
    color: var(--text-primary);
    background: var(--surface-card);
    border: 1px solid var(--border-default);
    border-radius: var(--radius-full);
    white-space: nowrap;
    padding: 9px var(--space-5);
    cursor: pointer;
    transition: border-color var(--transition-fast), background-color var(--transition-fast);
}

.boton-mas:hover {
    border-color: var(--bi);
    background: var(--surface-sunken);
}

.boton-mas:focus-visible {
    outline: 2px solid var(--bi);
    outline-offset: 2px;
}

.boton-mas:active {
    transform: scale(.97);
}

/* ── Formularios ─────────────────────────────────────────────
   Los usa /gestion y los va a usar el módulo de conocimiento: viven acá y no en
   style.css porque el segundo uso ya está a la vista. */

.formulario {
    display: flex;
    flex-direction: column;
    gap: var(--space-5);
    max-width: 680px;
    margin: var(--space-6) 0;
}

.formulario h2 {
    margin: 0;
    font-size: var(--text-xl);
    font-weight: var(--weight-semibold);
}

.campo {
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
}

.campo-etiqueta {
    font-size: var(--text-base);
    font-weight: var(--weight-semibold);
    color: var(--text-primary);
}

/* El asterisco es de apoyo visual, no la señal real: los campos llevan
   `required`, que es lo que anuncia el lector de pantalla y lo que impide el
   envío. */
.campo-obligatorio {
    color: var(--aviso-mant-fg);
    margin-left: 2px;
}

.campo-ayuda {
    font-size: var(--text-sm);
    color: var(--text-muted);
    line-height: 1.45;
}

.campo input,
.campo select,
.campo textarea {
    width: 100%;
    padding: var(--space-2) 11px;
    font-size: var(--text-md);
    font-family: inherit;
    color: var(--text-primary);
    background: var(--surface-card);
    border: 1px solid var(--border-default);
    border-radius: var(--radius-lg);
    outline: none;
    transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
}

.campo textarea {
    resize: vertical;
    line-height: 1.5;
}

.campo input:focus,
.campo select:focus,
.campo textarea:focus {
    border-color: var(--accent);
    box-shadow: 0 0 0 3px var(--accent-bg);
}

.campo-marca {
    flex-direction: row;
    align-items: flex-start;
    gap: var(--space-2);
}

.campo-marca input {
    width: auto;
    margin-top: 3px;
    accent-color: var(--accent);
}

.campo-marca > span {
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.grupo-campos {
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
    padding: var(--space-4);
    border: 1px solid var(--border-default);
    border-radius: var(--radius-xl);
}

.grupo-campos legend {
    padding: 0 var(--space-2);
    font-size: var(--text-base);
    font-weight: var(--weight-semibold);
    color: var(--text-secondary);
}

.acciones-formulario {
    display: flex;
    align-items: center;
    gap: var(--space-3);
}

.boton-guardar {
    font: inherit;
    font-size: var(--text-md);
    font-weight: var(--weight-semibold);
    color: var(--text-inverse);
    background: var(--accent);
    border: 1px solid transparent;
    border-radius: var(--radius-lg);
    padding: var(--space-2) var(--space-5);
    cursor: pointer;
    transition: filter var(--transition-fast);
}

.boton-guardar:hover {
    filter: brightness(1.08);
}

[data-theme="dark"] .boton-guardar,
[data-theme="contrast"] .boton-guardar {
    color: var(--bg);
}

/* Y lo mismo para quien no eligió tema, que es el caso por omisión.

   Sin este bloque, la regla de arriba sólo alcanza a quien tocó el selector: en
   «seguir al sistema» —el modo en el que está casi todo el mundo— el botón
   quedaba con texto claro sobre el violeta claro del acento oscuro, **2,07:1**
   contra el 4,5 que pide AA. No lo veía el verificador de contraste porque mide
   pares de tokens y esto sale de la combinación de dos reglas. */
@media (prefers-color-scheme: dark) {
    html:not([data-theme="light"]):not([data-theme="contrast"]) .boton-guardar {
        color: var(--bg);
    }
}

.boton-cancelar {
    font-size: var(--text-md);
    font-weight: var(--weight-semibold);
    color: var(--text-muted);
    text-decoration: none;
}

.boton-cancelar:hover {
    color: var(--text-primary);
}

/* El margen abajo y no arriba, por el mismo motivo que la franja de avisos:
   este mensaje —«Mis accesos admite hasta N tarjetas»— también se inserta
   arriba del catálogo, así que con el margen del otro lado se pegaba al primer
   título de sección.

   Es el mismo defecto y se ve mucho menos: hay que llegar al tope de favoritos
   para que aparezca alguna vez. */
.mensaje-error {
    display: flex;
    align-items: flex-start;
    gap: var(--space-2);
    margin: 0 0 var(--space-6);
    padding: var(--space-3) 14px;
    font-size: var(--text-base);
    font-weight: var(--weight-medium);
    line-height: 1.5;
    color: var(--aviso-mant-fg);
    background: var(--aviso-mant-bg);
    border: 1px solid transparent;
    border-radius: var(--radius-lg);
}

[data-theme="contrast"] .mensaje-error {
    border-color: #ffffff;
}

/* ── El campo «Ícono», con su vista previa ────────────────── */

/* El dibujo va al lado del campo y no debajo: es la comprobación de lo que se
   está escribiendo, y separado del texto por un renglón deja de leerse como su
   respuesta. */
.campo-con-vista {
    display: flex;
    align-items: center;
    gap: var(--space-2);
}

.vista-icono {
    display: flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    width: 38px;
    height: 38px;
    color: var(--text-secondary);
    background: var(--surface-sunken);
    border: 1px solid var(--border-default);
    border-radius: var(--radius-md);
}

.vista-icono .ti {
    width: 20px;
    height: 20px;
}

/* Un nombre que no está en el sprite. Con el color del aviso de mantenimiento y
   no con el del error: no impide guardar —el nombre puede existir en el próximo
   despliegue— y pintarlo de error diría que el formulario está mal. */
.campo-ayuda-alerta {
    color: var(--aviso-mant-fg);
    font-weight: var(--weight-medium);
}

/* ── La galería de íconos ─────────────────────────────────── */

/* `auto-fill` y no `auto-fit`: con pocos resultados filtrados, `auto-fit`
   colapsa las columnas vacías y estira las fichas que quedan hasta ocupar todo
   el ancho — una sola coincidencia salía como una ficha de 900px. */
.rejilla-iconos {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(124px, 1fr));
    gap: var(--space-2);
    margin: 0 0 var(--space-6);
    padding: 0;
    list-style: none;
}

.ficha-icono {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-2);
    width: 100%;
    padding: var(--space-4) var(--space-2);
    color: var(--text-secondary);
    background: var(--surface-card);
    border: 1px solid var(--border-default);
    border-radius: var(--radius-lg);
    cursor: pointer;
    /* Sólo color y borde. Animar la caja movería las setenta y siete fichas de
       la grilla en cada destello. */
    transition: background-color 120ms ease, border-color 120ms ease, color 120ms ease;
}

.ficha-icono:hover {
    color: var(--accent);
    border-color: var(--border-strong);
}

.ficha-icono .ti {
    width: 26px;
    height: 26px;
}

/* Los nombres largos —`ti-brand-onedrive`, `ti-heart-handshake`— parten por el
   guión en vez de desbordar la ficha o estirar la columna. */
.ficha-icono-nombre {
    font-size: var(--text-xs);
    color: var(--text-muted);
    line-height: 1.35;
    overflow-wrap: anywhere;
}

/* El destello de «copiado». El portapapeles no se ve: sin esto, tocar una ficha
   no produce ninguna señal y se toca dos veces. */
.ficha-icono-copiada {
    color: var(--text-inverse);
    background: var(--accent);
    border-color: var(--accent);
}

.ficha-icono-copiada:hover {
    color: var(--text-inverse);
    background: var(--accent);
    border-color: var(--accent);
}

.ficha-icono-copiada .ficha-icono-nombre {
    color: inherit;
}

/* Como `.boton-guardar`: en oscuro y en contraste el acento es claro, y el
   blanco encima no se lee. Con `--accent-fg` —el acento sobre el acento— el
   destello daba 1,3:1 y el nombre desaparecía mientras duraba. */
[data-theme="dark"] .ficha-icono-copiada,
[data-theme="dark"] .ficha-icono-copiada:hover,
[data-theme="contrast"] .ficha-icono-copiada,
[data-theme="contrast"] .ficha-icono-copiada:hover {
    color: var(--bg);
}

@media (prefers-color-scheme: dark) {
    html:not([data-theme="light"]):not([data-theme="contrast"]) .ficha-icono-copiada,
    html:not([data-theme="light"]):not([data-theme="contrast"]) .ficha-icono-copiada:hover {
        color: var(--bg);
    }
}

/* ── Tablas ──────────────────────────────────────────────── */

/* El marco que scrollea, **a cualquier ancho** y no sólo en pantallas chicas.
   Sin él la tabla desborda el envoltorio y empuja la página entera hacia la
   derecha — peor que un scroll, porque el contenido se va de la pantalla y no
   hay forma de traerlo de vuelta. La de Personas tiene siete columnas y no
   entra hasta cerca de los 850px: un portátil sin maximizar, una tablet, media
   pantalla de un equipo de sede.

   Antes esto lo hacía la propia tabla, con `display: block; overflow-x: auto`.
   Andaba el scroll y rompía el ancho: con `display: block` el `<thead>` y el
   `<tbody>` siguen siendo `table-row-group`, así que generan una **tabla
   anónima** adentro del bloque. El `width: 100%` se aplicaba al bloque; la
   tabla anónima quedaba en `width: auto`, o sea encogida al contenido. Las de
   pocas columnas usaban entre el 41% y el 52% del ancho disponible y se veían
   apretadas contra el margen izquierdo.

   No se podía arreglar desde ahí: a una caja anónima no la alcanza ningún
   selector. Hacía falta un elemento de verdad, que es éste.

   Sin `tabindex`: los navegadores actuales hacen foco por teclado en las cajas
   que scrollean, y ponerlo a mano dejaría una parada muerta en cada tabla que
   sí entra —que son casi todas—. */
.tabla-marco {
    overflow-x: auto;
}

.tabla-gestion {
    width: 100%;
    border-collapse: collapse;
    font-size: var(--text-base);
}

.tabla-gestion th {
    text-align: left;
    padding: var(--space-2) var(--space-3);
    font-size: var(--text-sm);
    font-weight: var(--weight-semibold);
    color: var(--text-muted);
    border-bottom: 1px solid var(--border-default);
}

.tabla-gestion td {
    padding: var(--space-3);
    border-bottom: 1px solid var(--border-default);
    vertical-align: top;
}

.tabla-gestion a {
    font-weight: var(--weight-semibold);
    color: var(--accent);
}

.tabla-gestion a:hover {
    text-decoration: underline;
}

/* La columna de texto de cada tabla: la que crece cuando sobra ancho.

   Sin esto el navegador reparte el sobrante **en proporción** entre todas las
   columnas, y en «Qué se abre» la de «Aperturas» —un número de dos dígitos—
   pasaba de 78 a 222px. El número quedaba solo en medio de la nada y había que
   cruzar la fila con el ojo para saber a qué tarjeta correspondía.

   Va en el `<td>` y no hace falta repetirlo en el `<th>`: con `table-layout`
   automático la columna que pide 100% se lleva todo el sobrante y las demás se
   quedan en su ancho natural. */
.celda-principal {
    width: 100%;
}

/* El texto secundario de una tabla, en sus dos formas: la columna corta, y el
   renglón que va debajo del título dentro de la columna que crece.

   El `nowrap` va **sólo en el `<td>`**, y ahí está toda la diferencia entre los
   dos usos. Como columna la clase marca fechas, números, correos y la lista de
   usos —que ya trae sus propios `<br>`—, y partir cualquiera de esos por la
   mitad es peor que el scroll. Como `<span>` marca texto de largo libre, que es
   justamente lo que tiene que partirse.

   Estaba en los dos, y la descripción de un grupo pagaba la diferencia: a
   1280px la tabla pedía 1509px dentro de un marco de 1080 y se iba 429px hacia
   la derecha. No fallaba nada y la pantalla andaba — sólo había que scrollear
   de costado para leer una columna que entraba de sobra. */
.celda-detalle {
    color: var(--text-muted);
}

td.celda-detalle {
    white-space: nowrap;
}

.celda-acciones {
    text-align: right;
    white-space: nowrap;
}

/* El número de orden, primera columna de las tres tablas del catálogo.

   `tabular-nums` y no la cifra por defecto: en varias fuentes el «1» es más
   angosto que el resto, y una columna de números que no alinean por la unidad
   se lee peor justamente cuando hay muchas filas, que es cuando este número
   sirve para algo.

   Angosta y apagada a propósito. Es un dato de referencia para acomodar, no
   algo que haya que mirar en cada fila: con el mismo peso que el nombre le
   compite la atención a lo que de verdad se viene a buscar. */
.columna-orden {
    width: 1%;
    text-align: right;
    white-space: nowrap;
    color: var(--text-muted);
    font-variant-numeric: tabular-nums;
}

/* ── Fichas del panel de estado ───────────────────────────────
   Un dato grande con su contexto arriba y abajo. El estado va por color de
   borde y no sólo por texto, pero el texto siempre está: un panel que dijera
   «verde» y nada más es inútil para quien no distingue los dos. */

.fichas {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
    gap: var(--space-3);
    margin-bottom: var(--space-4);
}

.ficha {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: 14px var(--space-4);
    background: var(--surface-card);
    border: 1px solid var(--border-default);
    border-left-width: 3px;
    border-radius: var(--radius-lg);
}

.ficha strong {
    font-size: var(--text-lg);
    font-weight: var(--weight-semibold);
}

.ficha-titulo {
    font-size: var(--text-xs);
    font-weight: var(--weight-bold);
    letter-spacing: .4px;
    text-transform: uppercase;
    color: var(--text-muted);
}

.ficha-nota {
    font-size: var(--text-sm);
    color: var(--text-muted);
    line-height: 1.45;
}

.ficha-bien {
    border-left-color: var(--aviso-nov-fg);
}

.ficha-mal {
    border-left-color: var(--aviso-mant-fg);
}

/* La franja de acciones del respaldo: el estado en curso y el botón. */
.acciones-estado {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    flex-wrap: wrap;
    margin-top: var(--space-4);
}

.marca-corriendo {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    font-size: var(--text-sm);
    font-weight: var(--weight-semibold);
    color: var(--aviso-info-fg);
    background: var(--aviso-info-bg);
    border-radius: var(--radius-full);
    white-space: nowrap;
    padding: 4px 12px;
}

.marca-corriendo .ti {
    width: 15px;
    height: 15px;
}

/* Confirmación de algo que salió bien. El error ya tiene `.mensaje-error`. */
.mensaje-listo {
    display: flex;
    align-items: flex-start;
    gap: var(--space-2);
    margin: 0 0 var(--space-6);
    padding: var(--space-3) 14px;
    font-size: var(--text-base);
    font-weight: var(--weight-medium);
    line-height: 1.5;
    color: var(--aviso-nov-fg);
    background: var(--aviso-nov-bg);
    border: 1px solid transparent;
    border-radius: var(--radius-lg);
}

/* El resultado que no salió mal ni salió bien: no pasó nada.

   «Ya había una publicación pedida», «el grupo ya estaba completo», «esa persona
   ya estaba así». Iban con el rojo de mantenimiento, al lado de fallas de
   verdad y con el mismo peso visual. Reusa los tokens del aviso informativo,
   que ya existen y ya están verificados en los tres temas. */
.mensaje-info {
    display: flex;
    align-items: flex-start;
    gap: var(--space-2);
    margin: 0 0 var(--space-6);
    padding: var(--space-3) 14px;
    font-size: var(--text-base);
    font-weight: var(--weight-medium);
    line-height: 1.5;
    color: var(--aviso-info-fg);
    background: var(--aviso-info-bg);
    border: 1px solid transparent;
    border-radius: var(--radius-lg);
}

/* En alto contraste los fondos son negro puro, o sea el mismo del documento: sin
   un borde visible el bloque desaparece como bloque y queda un texto suelto. El
   de error lo conserva por su color; éstos dos lo perdían. */
[data-theme="contrast"] .mensaje-listo,
[data-theme="contrast"] .mensaje-info {
    border-color: currentcolor;
}

/* Bajada de una sección del panel: explica qué mira ese bloque. */
.intro-seccion {
    margin: 0 0 var(--space-4);
    font-size: var(--text-base);
    color: var(--text-muted);
    line-height: 1.55;
}

/* El `nowrap` queda para pantallas chicas. En una ancha, con espacio de sobra,
   obligaría a scrollear por un correo largo que podría partirse. */
@media (max-width: 640px) {
    .tabla-gestion {
        white-space: nowrap;
    }
}

/* ── Curva de adopción del panel ──────────────────────────────
   Barras y no una línea: la serie tiene días en cero —fines de semana— y una
   línea entre dos puntos altos sugiere una transición que no ocurrió. Cada
   barra es un día y se lee de a uno.

   Sin biblioteca de gráficos: son treinta divs con una altura. Traer una
   dependencia de JavaScript para esto costaría más que todo el panel junto, y
   además la CSP no autoriza scripts de otro origen. */

.curva {
    display: flex;
    align-items: flex-end;
    gap: 3px;
    height: 90px;
    padding: var(--space-3) var(--space-4);
    background: var(--surface-card);
    border: 1px solid var(--border-default);
    border-radius: var(--radius-lg);
}

.curva-barra {
    flex: 1;
    /* El mínimo deja ver los días sin nadie: sin él, cero es indistinguible de
       «no hay dato» y la serie parece cortada. */
    height: max(var(--alto), 2px);
    background: var(--accent);
    border-radius: 2px 2px 0 0;
    opacity: .85;
}

.curva-barra:hover {
    opacity: 1;
}

/* El alto de cada barra, en pasos de 5%.

   Veintiuna clases y no un `style="--alto: 43%"` en la plantilla, que es lo
   que había: la CSP declara `style-src 'self'` y el navegador **bloquea el
   atributo style**, así que `--alto` no llegaba a definirse, `max(var(--alto),
   2px)` quedaba inválida y el gráfico salía sin una sola barra. Con 200 en la
   respuesta y el error sólo en la consola.

   Un nonce no lo arregla: no aplican a atributos `style`, únicamente a
   etiquetas `<style>`. Y las alternativas eran `unsafe-inline` o
   `unsafe-hashes`, o sea aflojar la política de todo el sitio para dibujar
   una barrita. Redondear al 5% no se nota en 40 píxeles de alto. */
.curva-h0 { --alto: 0%; }
.curva-h5 { --alto: 5%; }
.curva-h10 { --alto: 10%; }
.curva-h15 { --alto: 15%; }
.curva-h20 { --alto: 20%; }
.curva-h25 { --alto: 25%; }
.curva-h30 { --alto: 30%; }
.curva-h35 { --alto: 35%; }
.curva-h40 { --alto: 40%; }
.curva-h45 { --alto: 45%; }
.curva-h50 { --alto: 50%; }
.curva-h55 { --alto: 55%; }
.curva-h60 { --alto: 60%; }
.curva-h65 { --alto: 65%; }
.curva-h70 { --alto: 70%; }
.curva-h75 { --alto: 75%; }
.curva-h80 { --alto: 80%; }
.curva-h85 { --alto: 85%; }
.curva-h90 { --alto: 90%; }
.curva-h95 { --alto: 95%; }
.curva-h100 { --alto: 100%; }

/* El pie del paginador. Reusaba `.curva-pie` —el pie del gráfico— y por eso
   «Anterior» y «Siguiente» salían en el gris de 11px del contador, sin nada que
   los distinguiera del texto «51–100 de 1087» que tienen al lado. Son las
   únicas anclas de la pantalla que no se ven como anclas. */
.paginador {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin: var(--space-4) 0 0;
    font-size: var(--text-base);
    color: var(--text-muted);
}

.paginador a {
    color: var(--accent);
    font-weight: var(--weight-semibold);
    text-decoration: none;
}

.paginador a:hover {
    text-decoration: underline;
}

/* Señal de espera para los botones que abren un modal. Marcaban `aria-busy` y
   ninguna regla lo miraba, así que el clic no producía nada visible mientras se
   consulta al directorio — que puede tardar segundos. */
[aria-busy="true"] {
    opacity: .6;
    cursor: progress;
}

.curva-pie {
    display: flex;
    justify-content: space-between;
    margin: var(--space-2) 0 0;
    font-size: var(--text-xs);
    color: var(--text-muted);
}

/* ── Navegación entre las pantallas de gestión ────────────────
   Las dos siempre a la vista, con la actual marcada. Estaba como un enlace
   dentro del párrafo de intro: se leía como una nota al pie y no como el otro
   lugar donde se puede estar.

   `aria-current="page"` y no sólo el color: quien navega con lector necesita
   saber en cuál está, y el fondo no se lo dice. */

/* La barra dejó el encabezado y bajó a su propia fila, debajo del texto de
   bajada: son siete pestañas y en el encabezado no entraban.

   `flex-wrap` porque en un teléfono siete no entran en una línea, y sin esto la
   séptima se sale de la pantalla y arrastra el scroll horizontal de toda la
   página. El margen de abajo la separa del título de la sección, que pegado se
   leía como parte de la barra. */
.nav-gestion {
    display: flex;
    flex-wrap: wrap;
    gap: 2px;
    margin: 0 0 var(--space-6);
    background: var(--surface-sunken);
    border: 1px solid var(--border-default);
    border-radius: var(--radius-lg);
    padding: 2px;
}

.nav-gestion a {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    height: calc(var(--alto-control) - 6px);
    padding: 0 var(--space-3);
    font-size: var(--text-sm);
    font-weight: var(--weight-semibold);
    color: var(--text-muted);
    text-decoration: none;
    border-radius: var(--radius-md);
    transition: color var(--transition-fast), background var(--transition-fast);
}

.nav-gestion a:hover {
    color: var(--text-primary);
}

.nav-gestion a[aria-current="page"] {
    color: var(--accent-fg);
    background: var(--surface-card);
    box-shadow: var(--shadow-xs);
}

.nav-gestion .ti {
    width: 15px;
    height: 15px;
}

/* ── Bloque de configuración de una sección de gestión ───── */

/* Plegado por omisión cuando ya está configurado: es un ajuste que se toca una
   vez por año y no puede quedar por encima de la tabla que la gente viene a
   editar. Abierto cuando falta, para que se vea que existe. */

.bloque-config {
    margin: 0 0 var(--space-4);
    border: 1px solid var(--border-default);
    border-radius: var(--radius-lg);
    background: var(--surface-card);
}

.bloque-config > summary {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-3);
    font-size: var(--text-base);
    font-weight: var(--weight-semibold);
    color: var(--text-primary);
    cursor: pointer;
}

.bloque-config > summary .ti {
    width: 16px;
    height: 16px;
    flex: none;
    color: var(--text-muted);
}

/* El estado, en el renglón plegado: si hay que abrirlo para saber si está bien
   configurado, nadie lo abre y nadie se entera de que está mal. */
.config-resumen {
    margin-left: auto;
    font-size: 12.5px;
    font-weight: var(--weight-normal);
    color: var(--text-muted);
    text-align: right;
}

.bloque-config .formulario,
.bloque-config .mensaje-error {
    margin: 0 var(--space-3) var(--space-3);
}

/* El buscador de las cuatro pantallas de gestión que filtran una tabla.

   Eran dos clases —`.buscador-gestion` para el de Personas y `.buscador-tabla`
   para los otros tres— porque el de Personas vivía en la fila del título,
   pegado a la derecha con `margin-left: auto` y sin ancho declarado: le tocaba
   el hueco que sobrara, 253px contra los 420 de sus vecinos. Se pasa de Módulos
   a Personas con un clic en la pestaña de al lado, y el campo se movía de lugar
   y se achicaba un 40%.

   Con los cuatro en su propia fila la segunda clase ya no separaba nada, así que
   son una sola.

   `align-items: center` porque el «Limpiar» quedaba unos píxeles más arriba que
   el texto del campo: son dos alturas distintas en una fila sin alineación
   declarada. */
.buscador-gestion {
    position: relative;
    display: flex;
    align-items: center;
    gap: var(--space-2);
    margin: var(--space-3) 0 var(--space-4);
    max-width: var(--ancho-buscador);
}

.buscador-gestion .ti {
    position: absolute;
    left: 11px;
    top: 50%;
    transform: translateY(-50%);
    font-size: var(--text-lg);
    color: var(--text-muted);
    /* La lupa está encima del campo: sin esto, el clic en ella no enfoca. */
    pointer-events: none;
}

.buscador-gestion input {
    flex: 1;
    /* Sin esto la fila no cede: por omisión un hijo de flex no se encoge por
       debajo del ancho de su contenido, así que el campo mantenía su tamaño y
       empujaba al «Limpiar» fuera de lugar. */
    min-width: 0;
    height: var(--alto-control);
    /* La sangría izquierda es el lugar de la lupa. Va en el mismo `padding` que
       el resto y no en un `padding-left` aparte: declarados en dos reglas de
       igual especificidad, la de la forma corta pisaba a la otra y la lupa
       terminaba encima del texto. */
    padding: 0 var(--space-3) 0 34px;
    font-size: var(--text-sm);
    color: var(--text-primary);
    background: var(--surface-card);
    border: 1px solid var(--border-default);
    border-radius: var(--radius-lg);
    outline: none;
}

.buscador-gestion input:focus {
    border-color: var(--accent);
}

/* ── Selector de tema ─────────────────────────────────────────
   Sólo en la puerta web: en Teams manda el cliente, que avisa su tema por el
   SDK y notifica los cambios en vivo. */

/* ── Selector de tema ─────────────────────────────────────────
   Sólo en la puerta web: en Teams manda el cliente, que avisa su tema por el
   SDK y notifica los cambios en vivo.

   Es un `<details>` cuyo resumen se dibuja como el botón del engranaje, y cuyo
   contenido es un panel flotante con las cuatro opciones. La forma anterior era
   un `<select>` nativo, y se cambió porque la especificación sólo permite
   **texto** dentro de un `<option>`: con el sprite afuera, lo mejor que se podía
   poner era un carácter. */

.selector-tema {
    /* El ancla del panel, que va posicionado contra este contenedor. */
    position: relative;
}

/* El resumen ES el botón. Hereda la forma del engranaje por compartir
   `.volver-al-hub`; acá sólo se le sacan las marcas propias del `<summary>`. */
.selector-tema > summary {
    /* El triangulito, en sus dos formas: los navegadores modernos usan
       `::marker` y WebKit todavía dibuja el suyo con este pseudo-elemento. */
    list-style: none;
}

.selector-tema > summary::-webkit-details-marker {
    display: none;
}

/* Abierto, el botón se marca como el engranaje al pasarle por encima: sin esto
   no hay ninguna señal de cuál de los controles del encabezado abrió el panel. */
.selector-tema[open] > summary {
    color: var(--accent);
    border-color: var(--accent);
}

/* ── El panel ───────────────────────────────────────────────
   Flotante y **anclado a la derecha**: este control vive cerca del borde
   derecho de la pantalla, así que creciendo hacia la izquierda no se sale.

   `position: absolute` y no `fixed`: tiene que moverse con la página al
   scrollear, como cualquier menú pegado a su botón. */
.panel-tema {
    position: absolute;
    top: calc(100% + var(--space-2));
    right: 0;
    z-index: var(--z-popover);
    min-width: var(--ancho-panel-tema);
    padding: var(--space-2);
    background: var(--surface-card);
    border: 1px solid var(--border-default);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-lg);
}

/* El `<fieldset>` existe por el `<legend>`, que es lo que le da nombre al grupo
   para un lector de pantalla. Sin borde ni relleno: los del user-agent dibujan
   un marco dentro del panel. */
.panel-tema fieldset {
    display: flex;
    flex-direction: column;
    gap: 2px;
    margin: 0;
    padding: 0;
    border: 0;
}

.opcion-tema {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-2) var(--space-3);
    border-radius: var(--radius-md);
    font-size: var(--text-base);
    color: var(--text-secondary);
    cursor: pointer;
    /* La fila entera es el objetivo del clic, no sólo el texto: un blanco de
       208×34 se acierta sin mirar. */
    user-select: none;
}

/* El radio no se ve: lo que marca la elección es la fila resaltada. Se esconde
   con `.solo-lectores` y no con `display: none` —que lo sacaría del árbol de
   accesibilidad y del orden de tabulación— porque es el control de verdad: es
   lo que se recorre con las flechas y lo que envía el formulario. */
.opcion-tema input[type="radio"] {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    clip-path: inset(50%);
    white-space: nowrap;
}

.opcion-tema .ti {
    width: 17px;
    height: 17px;
    flex: none;
    color: var(--text-muted);
}

.opcion-tema:hover {
    background: var(--surface-sunken);
    color: var(--text-primary);
}

/* El foco va en la fila y no en el radio, que es de un píxel: sin esto, quien
   recorre con las flechas no ve dónde está parado. */
.opcion-tema:has(input:focus-visible) {
    outline: 2px solid var(--accent);
    outline-offset: -2px;
}

.opcion-tema:has(input:checked) {
    color: var(--accent-fg);
    background: var(--accent-bg);
    font-weight: var(--weight-semibold);
}

.opcion-tema:has(input:checked) .ti {
    color: var(--accent-fg);
}

/* «Aplicar» lo esconde el JavaScript, no el CSS: sin scripts es lo único que
   envía. Con scripts, elegir aplica y el panel se cierra solo. */
.panel-tema > button {
    width: 100%;
    margin-top: var(--space-2);
    justify-content: center;
}

/* **En pantallas chicas el panel se cuelga del encabezado, no del botón.**

   El panel crece hacia la izquierda porque en una pantalla ancha el control vive
   pegado al borde derecho. Pero `.usuario` lleva `flex-wrap`: por debajo de
   cierto ancho su fila baja y el selector queda a la **izquierda de todo**, y
   ahí el mismo `right: 0` lo mandaba a empezar en −158px — medio panel fuera de
   la pantalla, sin scroll que lo alcance porque flota. Medido a 320px.

   El primer intento fue dar vuelta el anclaje por debajo de 640, y estaba mal:
   **a 640 el encabezado todavía no envuelve** —el botón está en x=464— así que
   el panel anclado a la izquierda se salía por el otro lado. El ancho de la
   ventana no dice dónde quedó el botón, y ése era el dato que hacía falta.

   La salida es no depender de dónde quedó: se corre el bloque contenedor al
   `.encabezado`, que ocupa el ancho del contenido. Ahí `right: 0` es el borde
   derecho de la página, que está dentro de la pantalla **envuelva o no**, y el
   corte deja de tener que adivinar nada.

   El precio es que abajo de 640 el panel no cuelga exactamente del botón sino
   del encabezado. A ese ancho el encabezado ya es una caja compacta y el botón
   queda dentro del tramo horizontal del panel, así que la relación se sigue
   leyendo. */
@media (max-width: 640px) {
    .selector-tema {
        position: static;
    }

    .encabezado {
        position: relative;
    }

    /* `100%` pasa a ser el alto del encabezado: el panel aparece debajo de él y
       no debajo del botón. */
    .panel-tema {
        max-width: calc(100% - 2 * var(--space-2));
    }
}


/* ── Modal de grupos ─────────────────────────────────────────
   `<dialog>` nativo: trae el foco atrapado, el cierre con Escape y el fondo
   inerte sin una línea de JavaScript. Lo único que hay que hacer es abrirlo. */

.modal {
    /* El navegador centra un `<dialog>` con `margin: auto`, y el `* { margin: 0 }`
       del reset se lo lleva puesto: el modal aparecía pegado a la esquina
       superior izquierda. No es un caso raro del reset — es lo que le pasa a
       cualquier elemento que dependa del margen del user-agent. */
    margin: auto;
    width: min(560px, calc(100vw - 2 * var(--space-5)));
    max-height: 80vh;
    overflow-y: auto;
    padding: var(--space-5);
    color: var(--text-primary);
    background: var(--surface-card);
    border: 1px solid var(--border-default);
    border-radius: var(--radius-xl);
    box-shadow: var(--shadow-md);
}

/* El navegador pinta el fondo con su propio color por omisión, que no sabe del
   tema: en oscuro quedaba un velo claro sobre una página oscura. */
.modal::backdrop {
    background: rgba(0, 0, 0, .55);
}

/* El de integrantes lista un padrón entero, con tres datos por fila. Con 560px
   el estado de cada persona cae debajo del correo y la lista se lee como el
   triple de larga de lo que es. */
.modal-ancho {
    width: min(720px, calc(100vw - 2 * var(--space-5)));
}

.lista-integrantes li {
    display: flex;
    align-items: baseline;
    gap: var(--space-2);
}

/* El correo cede el espacio que haga falta, con puntos suspensivos. Sin esto la
   fila hace `wrap` y el estado cae abajo — pero sólo en las filas cuyo texto es
   largo, así que la columna de estados queda alineada en unas filas y no en
   otras, que se lee como si estuvieran diciendo cosas distintas. */
.lista-integrantes .grupo-desc {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.lista-integrantes .marca-abierta,
.lista-integrantes .marca-baja,
.lista-integrantes .marca-restringida {
    flex: none;
    margin-left: auto;
}

/* Las tres marcas se leen igual dentro de esta lista. En las tablas de gestión
   «dada de baja» va en versalitas porque compite con el nombre de una tarjeta;
   acá son tres estados del mismo eje y uno en mayúsculas parece más grave. */
.lista-integrantes .marca-baja {
    font-size: 12px;
    font-weight: var(--weight-normal);
    text-transform: none;
    letter-spacing: 0;
}

/* La acción que escribe, separada de la lista que la explica. */
.confirmar-traida {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2) var(--space-3);
    margin: 0 0 var(--space-4);
    padding: var(--space-3);
    border: 1px solid var(--border-default);
    border-radius: var(--radius-lg);
    background: var(--bg);
}

.confirmar-traida .campo-ayuda {
    flex: 1 1 260px;
    margin: 0;
}

.modal-encabezado {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    margin-bottom: var(--space-3);
}

.modal-encabezado h2 {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    margin: 0;
    font-size: var(--text-xl);
    font-weight: var(--weight-semibold);
}

.modal-cerrar {
    margin-left: auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: var(--alto-control);
    height: var(--alto-control);
    font-size: var(--text-lg);
    color: var(--text-muted);
    background: transparent;
    border: 1px solid var(--border-default);
    border-radius: var(--radius-lg);
    cursor: pointer;
}

.modal-cerrar:hover {
    color: var(--text-primary);
    border-color: var(--accent);
}

.modal-bajada {
    margin: 0 0 var(--space-4);
    font-size: var(--text-base);
    color: var(--text-muted);
    line-height: 1.5;
}

/* ── Mi perfil ────────────────────────────────────────────────
   El modal que abre la foto del encabezado. Reemplaza al botón de «Mis grupos»
   que estaba al lado: los dos contestaban a qué tengo acceso y por qué. */

/* La foto, en el único lugar donde se ve a más de 34px. El resto lo hereda de
   `.avatar`, que es la misma pieza del encabezado — sin esto habría dos cajas
   de iniciales con estilos parecidos y ninguna razón para que se parezcan. */
.avatar-grande {
    width: 64px;
    height: 64px;
    border-radius: var(--radius-xl);
    font-size: var(--text-2xl);
}

/* `min-width: 0` para que un nombre largo no empuje el botón de cerrar fuera
   del modal: por omisión un hijo de flex no se encoge por debajo del ancho de
   su contenido. */
.perfil-identidad {
    min-width: 0;
}

/* La cuenta, escrita. Hasta ahora estaba sólo en el `title` del avatar, que en
   una tablet no se puede leer. `anywhere` porque un UPN largo es una sola
   palabra y en 320px se sale del modal. */
.perfil-cuenta {
    margin: 2px 0 0;
    font-size: var(--text-base);
    color: var(--text-muted);
    overflow-wrap: anywhere;
}

/* ── La ficha del directorio ──────────────────────────────────────────────
   Cargo, área, oficina y teléfono. Grilla de dos columnas en vez de una lista
   con `<dt>` y `<dd>` uno debajo del otro: son cuatro pares cortos y apilados
   ocuparían ocho renglones para lo que entra en cuatro.

   `max-content` en la primera columna la mide por la etiqueta más larga
   —«Teléfono»— y deja el resto para el valor, que es el que puede necesitarlo:
   «Director Operativo - SIAC» no entra en 320px y tiene que poder envolver. */
.perfil-ficha {
    display: grid;
    grid-template-columns: max-content 1fr;
    gap: var(--space-2) var(--space-4);
    margin: 0 0 var(--space-5);
    padding: var(--space-3);
    font-size: var(--text-base);
    line-height: 1.4;
    background: var(--surface-sunken);
    border-radius: var(--radius-lg);
}

.perfil-ficha dt {
    color: var(--text-muted);
}

.perfil-ficha dd {
    margin: 0;
    /* Un teléfono o un nombre de oficina largo es una sola palabra y en 320px
       se sale del modal, igual que el UPN de arriba. */
    overflow-wrap: anywhere;
}

/* Lo que el directorio no tiene cargado, y se muestra igual: en blanco la fila
   parecería un error de la pantalla, y escondida daría una ficha que se lee
   completa cuando no lo está. Medido sobre cuarenta personas de la UAI, el
   cargo está cargado en una sola.

   Atenuado y en minúscula, que es lo que lo separa de un valor real —los de
   Entra vienen capitalizados—. En el tema de alto contraste los dos grises son
   blanco y la diferencia desaparece; ahí lo dice la palabra, que es justamente
   para lo que está escrita. */
.perfil-ficha .sin-cargar {
    color: var(--text-muted);
}

.perfil-rol {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--space-2);
    margin: 0 0 var(--space-4);
}

.perfil-rol-origen {
    font-size: var(--text-sm);
    color: var(--text-muted);
}

/* Desde cuándo y por qué puerta. `align-items: flex-start` y no `center`: el
   texto ocupa dos o tres renglones y con el ícono centrado verticalmente queda
   flotando en el medio del párrafo en vez de al lado de su primera línea. */
.perfil-historia {
    display: flex;
    align-items: flex-start;
    gap: var(--space-2);
    margin: 0 0 var(--space-5);
    padding: var(--space-3);
    font-size: var(--text-base);
    line-height: 1.5;
    color: var(--text-muted);
    background: var(--surface-sunken);
    border-radius: var(--radius-lg);
}

.perfil-historia .ti {
    margin-top: 3px;
    font-size: var(--text-lg);
    color: var(--acento-texto);
}

/* Menor que el `<h2>` del encabezado: es una sección de adentro del modal, no
   otro título del mismo peso. */
.perfil-titulo {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    margin: 0 0 var(--space-2);
    font-size: var(--text-md);
    font-weight: var(--weight-semibold);
}

.perfil-titulo .ti {
    color: var(--acento-texto);
}

/* El diagnóstico: cuándo se resolvieron los grupos y por qué camino. Va al pie
   y en el tono de lo secundario porque casi nunca hace falta — pero el día que
   alguien no ve una tarjeta es lo único que distingue «no pertenecés a ese
   grupo» de «no se pudo preguntar». */
.perfil-nota {
    margin: var(--space-4) 0 0;
    padding-top: var(--space-3);
    border-top: 1px solid var(--border-default);
    font-size: var(--text-sm);
    color: var(--text-muted);
    line-height: 1.5;
}

.lista-grupos {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
    margin: 0;
    padding: 0;
    list-style: none;
}

.lista-grupos li {
    padding: var(--space-3);
    background: var(--surface-sunken);
    border-radius: var(--radius-lg);
}

.grupo-nombre {
    display: block;
    font-weight: var(--weight-semibold);
    /* Un GUID sin resolver es una sola palabra de 36 caracteres. */
    overflow-wrap: anywhere;
}

/* El que no se pudo resolver se muestra igual, con su GUID y en el tono de lo
   secundario: una lista a la que le faltan grupos es peor que una con un
   identificador crudo, porque a la primera no se le nota lo que falta. */
.grupo-sin-resolver {
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: var(--text-sm);
    font-weight: var(--weight-semibold);
    color: var(--text-muted);
}

.grupo-desc {
    display: block;
    margin-top: 2px;
    font-size: var(--text-base);
    color: var(--text-muted);
    line-height: 1.45;
}

/* En la tabla de grupos, acotada a dos líneas.

   Un grupo de Entra admite 1024 caracteres de descripción, y con 415 la fila
   pasa de 64 a 123px de alto. Sin el recorte, dejar que el texto se parta
   cambia una tabla que se va de costado por una que es un muro de texto — y la
   descripción es contexto, no el dato que se viene a buscar acá.

   Calificado por la tabla y no puesto sobre `.grupo-desc` a secas: la misma
   clase dibuja los grupos de los modales, que es donde alguien la lee entera.

   `display: -webkit-box` pisa el `block` de arriba y es lo que activa el
   recorte **por el camino viejo**; sin él, `-webkit-line-clamp` no hace nada.
   Van las dos formas de la propiedad porque conviven: en el Chromium de hoy
   —el que corre en el cliente de escritorio de Teams— el `display` computado
   termina en `flow-root` y no en `-webkit-box`, o sea que ahí gana la estándar
   y la prefijada queda de red para lo que no la entienda.

   El `white-space` va declarado y no heredado: debajo de 640px la tabla entera
   se pone `nowrap` —para que las columnas cortas no se partan en un teléfono—
   y eso alcanza a todo lo que tenga adentro. Sin esta línea, en 375px la
   descripción volvía a un solo renglón de 2405px, la tabla pedía 2735 y
   quedaban 2408px de scroll lateral. El recorte tampoco salvaba nada: sobre una
   sola línea no hay dos que recortar. */
.tabla-gestion .grupo-desc {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    overflow: hidden;
    white-space: normal;
}

/* ── Aviso flotante ──────────────────────────────────────── */

/* Lo que dice una tarjeta bloqueada al tocarla. Abajo y centrado: arriba
   taparía el encabezado, y al costado se confunde con las notificaciones del
   sistema operativo.

   Sale del flujo y no empuja nada, así que aparecer no mueve la grilla debajo
   del dedo de quien acaba de tocar. */
.aviso-flotante {
    position: fixed;
    left: 50%;
    bottom: var(--space-5);
    z-index: var(--z-toast);
    transform: translate(-50%, calc(100% + var(--space-8)));
    max-width: min(520px, calc(100vw - 2 * var(--space-4)));
    margin: 0;
    padding: var(--space-3) var(--space-4);
    font-size: var(--text-base);
    line-height: 1.45;
    color: var(--nota-acceso-fg);
    background: var(--nota-acceso-bg);
    border: 1px solid var(--nota-acceso-fg);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-md);
    /* `visibility` además de la posición: sin ella el elemento sigue siendo
       clicable fuera de pantalla y tapa lo que haya en esa franja. */
    visibility: hidden;
    transition: transform .18s ease, visibility .18s;
}

.aviso-flotante.visible {
    transform: translate(-50%, 0);
    visibility: visible;
}

/* Quien pidió menos movimiento recibe el cartel igual, sin el deslizamiento. */
@media (prefers-reduced-motion: reduce) {
    .aviso-flotante {
        transition: none;
    }
}


/* ── Los campos en una pantalla táctil ────────────────────────
   iOS **agranda la página** al enfocar un campo cuya letra mide menos de 16px,
   y no la vuelve a achicar al salir. Se toca el buscador, la página se corre y
   deja medio catálogo afuera, y para volver hay que pellizcar a mano.

   No es un asunto sólo de la puerta web: la pestaña de Teams en iPhone y iPad
   se dibuja en un WKWebView, que es el mismo motor de Safari. En el escritorio
   —donde corre casi todo el uso de Teams, sobre WebView2— esto no pasa.

   La otra forma de evitarlo es `maximum-scale=1` en el viewport, y es la
   equivocada: apaga también el pellizco para agrandar, que es la única
   herramienta que le queda a quien ve poco. Lo prohíbe WCAG 1.4.4.

   `(pointer: coarse)` y no un ancho de pantalla: lo que dispara el zoom es el
   dispositivo, no el tamaño de la ventana. Y sube sólo ahí porque en un
   escritorio 16px dejaría el selector de tema más alto que el «Salir» de al
   lado, arreglando un problema que en esa pantalla no existe.

   Va al final del archivo a propósito: una media query no suma especificidad,
   así que estas reglas ganan por venir después de las que pisan. */
@media (pointer: coarse) {
    .buscador input,
    .buscador-gestion input,
    .campo input,
    .campo select,
    .campo textarea {
        font-size: 16px;
    }
}


/* La fila del interruptor de una sección: estado a la izquierda, acción a la
   derecha. Se usa en Reportes para prender y apagar la sección entera.

   `flex-wrap` porque a 320px la pastilla del estado y el botón no entran en una
   línea, y sin envolver el botón se sale de la pantalla. */
.fila-interruptor {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--space-3);
    margin-bottom: var(--space-4);
}

/* Qué se está eligiendo, delante de los botones. Los interruptores de
   Reportes y de la agenda no la llevan —ahí el propio botón dice qué
   hace— pero una fila de opciones sin etiqueta no dice de qué son. */
.etiqueta-interruptor {
    font-size: var(--text-sm);
    font-weight: var(--weight-semibold);
    color: var(--text-secondary);
}

/* La consecuencia de lo que se elige, al final de la fila. Va acá y no
   en un `title`: con el mouse encima aparece en escritorio y nunca en
   una tablet, que es donde más se administra el hub. */
.nota-interruptor {
    font-size: var(--text-xs);
    color: var(--text-muted);
}


/* De qué clase es un grupo, al lado de su nombre. Discreta a propósito: el dato
   que se viene a buscar es el nombre, y esto explica por qué ese grupo hace lo
   que hace. */
.tipo-de-grupo {
    display: inline-block;
    margin-left: var(--space-2);
    padding: 1px var(--space-2);
    font-size: var(--text-xs);
    font-weight: var(--weight-semibold);
    color: var(--text-muted);
    background: var(--surface-sunken);
    border: 1px solid var(--border-default);
    border-radius: var(--radius-full);
    white-space: nowrap;
    vertical-align: middle;
}


/* ── La ficha del reporte ─────────────────────────────────────
   El modal que abre la tarjeta de un reporte: cómo abrirlo arriba, y debajo el
   instructivo que explica cómo se lee.

   Vive acá y no en `style.css` porque no es de una pantalla: el mismo bloque de
   prosa —`.ficha-instructivo`— lo usan la ficha y la vista previa del formulario
   de gestión, y son justamente los dos lugares que TIENEN que verse igual. */

/* Dos zonas: la de arriba no se mueve, la de abajo scrollea.

   `.modal` da `padding` y `overflow-y: auto` al `<dialog>` entero, que es lo
   correcto para los otros tres modales. Acá no: un instructivo largo empujaría
   el nombre del reporte y el botón de abrirlo fuera de la pantalla, y quien
   entró justamente a abrirlo se quedaría buscando el botón hacia arriba. Por eso
   el `<dialog>` no scrollea —`overflow: hidden`— y el padding pasa a cada zona.

   `max-height: 80vh` lo hereda de `.modal`, y dentro del iframe de Teams `vh`
   es el alto del iframe, que es lo que corresponde. */
.modal-ficha {
    display: flex;
    flex-direction: column;
    /* 760 y no más. El instructivo es prosa, y pasada la marca de unos noventa
       caracteres por línea el ojo pierde el renglón al volver — es la razón por
       la que ningún sitio de documentación usa el ancho completo de la pantalla.
       Con el cuerpo de 14px de abajo, 760 son ~89 caracteres: el máximo que se
       puede aprovechar sin ponerle un tope aparte a la columna de texto y dejar
       margen vacío a la derecha. */
    width: min(760px, calc(100vw - 2 * var(--space-5)));
    /* Pisa el 80vh de `.modal`: un instructivo largo se lee de arriba abajo y
       cada franja de alto es una sección menos que scrollear.

       Medido sobre un instructivo real en 1440x900: la zona que scrollea pasa
       de 528 a 600px, un 14% más. **No es más que eso**, y conviene que quede
       escrito: el cuerpo más grande de abajo también estira el documento, así
       que la fracción que se ve por pantalla queda casi igual. Lo que se gana de
       verdad son 140px de ancho y una letra que se lee sin esfuerzo. */
    max-height: 88vh;
    padding: 0;
    overflow: hidden;
}

.ficha-fija {
    flex: none;
    padding: var(--space-5) var(--space-5) var(--space-4);
    border-bottom: 1px solid var(--border-default);
}

/* `min-height: 0` y no sólo `flex: 1`: por omisión un hijo de flex no se
   encoge por debajo de su contenido, así que sin esto el cuerpo crece hasta el
   largo del instructivo, se lleva el `max-height` del modal por delante y el
   scroll vuelve a ser el del `<dialog>` entero — o sea, el problema original,
   pero ahora escondido detrás de un layout que parece bien. */
.ficha-cuerpo {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    padding: var(--space-5);
}

/* El anillo de foco por dentro. El cuerpo es enfocable —si no, con teclado no
   hay forma de scrollearlo— y con el `outline-offset: 2px` del reset el anillo
   quedaba mordido por el borde del modal, que recorta. */
.ficha-cuerpo:focus-visible {
    outline-offset: -2px;
}

/* El ícono del reporte, del tamaño del avatar del perfil: son el mismo lugar de
   la misma caja y desalinearlos haría que los dos modales se lean como piezas
   de sistemas distintos.

   Hereda de `.icono`, que es la misma pieza de la tarjeta: acá sólo se dice en
   qué se diferencia, que es el tamaño. El color lo pone `.acento-reportes
   .icono` —el mismo ámbar de la tarjeta— porque el `<dialog>` lleva esa clase.
   Declararlo también acá serían dos fuentes para el mismo color, y la que
   ganara dependería del orden en que se carguen las hojas. */
.icono.ficha-icono {
    width: 40px;
    height: 40px;
    border-radius: var(--radius-lg);
    font-size: 20px;
}

/* Ídem `.perfil-identidad`: sin `min-width: 0` un título largo empuja el botón
   de cerrar fuera del modal. */
.ficha-identidad {
    min-width: 0;
}

.ficha-area {
    margin: 2px 0 0;
    font-size: var(--text-xs);
    font-weight: var(--weight-semibold);
    color: var(--text-muted);
    text-transform: uppercase;
    letter-spacing: .04em;
}

/* La acción principal, y se ve como tal. Arriba del instructivo: quien ya sabe
   qué es este reporte viene a abrirlo, y hacerle recorrer el manual para
   encontrar el botón convierte una mejora en un peaje. */
.ficha-abrir {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    margin-bottom: var(--space-5);
    padding: var(--space-2) var(--space-5);
    font-size: var(--text-md);
    font-weight: var(--weight-semibold);
    color: var(--text-inverse);
    background: var(--accent);
    border-radius: var(--radius-lg);
    text-decoration: none;
    transition: filter var(--transition-fast);
}

.ficha-abrir:hover {
    filter: brightness(1.08);
}

/* El mismo arreglo que `.boton-guardar`: en oscuro y en contraste el acento es
   claro, así que el texto blanco encima no se lee. Van los dos selectores y
   además el `:root:not(...)` de abajo, porque quien no eligió tema no tiene el
   atributo puesto. */
[data-theme="dark"] .ficha-abrir,
[data-theme="contrast"] .ficha-abrir {
    color: var(--bg);
}

/* Y lo mismo para quien no eligió tema, que es donde está casi todo el mundo.
   Sin esto la regla de arriba sólo alcanza a quien tocó el selector, y en
   «seguir al sistema» el botón queda con texto claro sobre el violeta claro del
   acento oscuro. Es el mismo par de reglas que `.boton-guardar`. */
@media (prefers-color-scheme: dark) {
    html:not([data-theme="light"]):not([data-theme="contrast"]) .ficha-abrir {
        color: var(--bg);
    }
}

/* El reporte anunciado que todavía no existe. Sobre el botón y no debajo: es lo
   que cambia el significado de lo que ese botón hace. */
.ficha-reservado {
    display: flex;
    align-items: flex-start;
    gap: var(--space-2);
    margin: 0 0 var(--space-3);
    padding: var(--space-3);
    font-size: var(--text-base);
    line-height: 1.5;
    color: var(--nota-acceso-fg);
    background: var(--nota-acceso-bg);
    border-radius: var(--radius-lg);
}

.ficha-reservado svg {
    flex: none;
    width: 16px;
    height: 16px;
    margin-top: 2px;
}

.ficha-sin-instructivo {
    margin: 0;
}

/* ── La prosa del instructivo ─────────────────────────────────
   Lo que sale del Markdown acotado, y nada más: si acá hay un selector para una
   etiqueta que `marcado.py` no emite, sobra. Y al revés — una etiqueta nueva en
   el renderizador sin su regla acá sale con los estilos del navegador, que en
   modo oscuro significa texto negro sobre fondo oscuro. */

/* Un punto más grande que el resto del hub, y es deliberado: el instructivo es
   lo único de esta pantalla que se **lee**, en vez de mirarse. El resto son
   rótulos, títulos de tarjeta y descripciones de un renglón, para los que 13px
   está bien. Un manual de dos pantallas, no.

   Es además lo que hace utilizable el ancho de 760: con 13px esa medida daría
   ~95 caracteres por línea, pasada la marca donde el ojo pierde el renglón. */
.ficha-instructivo {
    font-size: var(--text-md);
    line-height: 1.65;
    color: var(--text-secondary);
}

.ficha-instructivo > :first-child {
    margin-top: 0;
}

.ficha-instructivo > :last-child {
    margin-bottom: 0;
}

/* Los tres niveles, y que se distingan a un metro de la pantalla.

   Un instructivo largo se recorre saltando de título en título, así que tres
   niveles que sólo se diferencian por uno o dos píxeles de cuerpo no sirven de
   nada: quien lo lee no puede saber si volvió al nivel de arriba o sigue dentro
   de la misma sección. Cada uno cambia por **tres** señales a la vez —tamaño,
   tratamiento y separación— y no sólo por el tamaño.

     h3  (#)    grande, con una línea debajo que corta la sección
     h4  (##)   mediano y sólido, sin línea
     h5  (###)  chico, versalitas y en el color de acento

   El color del acento en el tercero no es decoración: es lo que impide que se
   confunda con un párrafo en negrita, que es el error más fácil de cometer en
   el nivel más chico. */

.ficha-instructivo h3,
.ficha-instructivo h4,
.ficha-instructivo h5 {
    color: var(--text-primary);
    line-height: 1.3;
}

.ficha-instructivo h3 {
    margin: var(--space-8) 0 var(--space-3);
    padding-bottom: var(--space-2);
    font-size: var(--text-xl);
    font-weight: var(--weight-bold);
    letter-spacing: -.01em;
    /* `--border-strong` y no `--border-default`: el de omisión es el que separa
       cajas que ya se distinguen por su fondo, y acá tiene que cortar la lectura
       sobre la misma superficie. Con el suave la línea se perdía en claro y el
       título de primer nivel se leía igual que el de segundo. */
    border-bottom: 1px solid var(--border-strong);
}

.ficha-instructivo h4 {
    margin: var(--space-6) 0 var(--space-2);
    font-size: var(--text-lg);
    font-weight: var(--weight-semibold);
}

.ficha-instructivo h5 {
    margin: var(--space-5) 0 var(--space-2);
    font-size: var(--text-sm);
    font-weight: var(--weight-bold);
    text-transform: uppercase;
    letter-spacing: .07em;
    color: var(--acento-texto);
}

.ficha-instructivo p {
    margin: 0 0 var(--space-3);
}

.ficha-instructivo ul,
.ficha-instructivo ol {
    margin: 0 0 var(--space-3);
    /* El `padding: 0` del reset le saca la sangría a toda lista de la
       aplicación. En el resto no importa —son filas de tarjetas sin marcador—,
       pero acá el marcador sí se dibuja y sin sangría queda cortado contra el
       borde izquierdo del modal. */
    padding-left: var(--space-5);
}

.ficha-instructivo li {
    margin-bottom: var(--space-1);
}

.ficha-instructivo strong {
    font-weight: var(--weight-semibold);
    color: var(--text-primary);
}

.ficha-instructivo code {
    padding: 1px var(--space-1);
    font-family: var(--font-mono);
    font-size: .92em;
    color: var(--text-primary);
    background: var(--surface-sunken);
    border: 1px solid var(--border-default);
    border-radius: var(--radius-sm);
}

.ficha-instructivo a {
    color: var(--acento-texto);
    text-decoration: underline;
    text-underline-offset: 2px;
}

/* ── La vista previa, en el formulario de gestión ─────────────
   El recuadro nace oculto y lo muestra `instructivo.js`: sin JavaScript sería
   un rótulo permanente sobre una caja que no se llena nunca. */

.instructivo-previa {
    margin-top: var(--space-3);
    padding: var(--space-4);
    background: var(--surface-sunken);
    border: 1px dashed var(--border-strong);
    border-radius: var(--radius-lg);
}

.instructivo-previa .campo-etiqueta {
    display: block;
    margin-bottom: var(--space-2);
}

/* El contador de caracteres. Discreto mientras no pasa nada, y del color de los
   avisos cuando el texto tocó el tope — que es el único momento en que hay algo
   que hacer al respecto. */
.instructivo-cuenta {
    font-variant-numeric: tabular-nums;
}

.instructivo-cuenta.en-el-tope {
    font-weight: var(--weight-semibold);
    color: var(--aviso-mant-fg);
}


/* ── El calendario de la agenda, en modal ─────────────────────
   Lo abre «Ver todo» de la portada, en las dos puertas. Las dos zonas son las
   mismas que la ficha del reporte y por el mismo motivo: un año entero no entra
   en pantalla, y sin esto el nombre y el selector de año se irían hacia arriba
   justo cuando hacen falta.

   Los estilos del contenido —`.chips-agenda`, `.agenda-completa`,
   `.agenda-mes-titulo`— no se repiten acá: son los mismos que en la página, que
   es el punto de que las dos dibujen el mismo fragmento. */

.modal-agenda {
    display: flex;
    flex-direction: column;
    /* El mismo ancho que la ficha del reporte. Acá el límite de la prosa no
       aplica —es una lista, aguanta cualquier ancho— pero dos anchos distintos
       para dos piezas del mismo sistema se notan al abrir una después de la
       otra. */
    width: min(760px, calc(100vw - 2 * var(--space-5)));
    max-height: 88vh;
    padding: 0;
    overflow: hidden;
}

.agenda-fija {
    flex: none;
    padding: var(--space-5) var(--space-5) var(--space-4);
    border-bottom: 1px solid var(--border-default);
}

/* `min-height: 0` por lo mismo que en la ficha: sin esto el cuerpo crece hasta
   el largo del calendario, se lleva por delante el `max-height` del modal y el
   scroll vuelve a ser el del `<dialog>` entero. */
.agenda-cuerpo {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    padding: var(--space-5);
}

.agenda-cuerpo:focus-visible {
    outline-offset: -2px;
}

/* El primer hijo del cuerpo trae su propio margen superior —la barra del ciclo,
   o las pastillas si no hay ciclo— y sumado al padding queda un hueco que se
   lee como un error de alineación. */
.agenda-cuerpo > :first-child {
    margin-top: 0;
}

/* Del tamaño del ícono de la ficha, para que los dos modales se lean como
   piezas del mismo sistema. El color sale del acento del hub y no se declara:
   este calendario es institucional, no de una sección. */
.icono.agenda-icono {
    width: 40px;
    height: 40px;
    border-radius: var(--radius-lg);
    font-size: 20px;
}

.agenda-identidad {
    min-width: 0;
}

.agenda-anio {
    margin: 2px 0 0;
    font-size: var(--text-xs);
    font-weight: var(--weight-semibold);
    color: var(--text-muted);
    text-transform: uppercase;
    letter-spacing: .04em;
}


/* ── Las consultas: el botón, el panel y el hilo ──────────────
   Viven acá y no en `style.css` porque las dibujan tres pantallas: el modal
   que abre el botón del encabezado, la página `/consultas` y —más adelante—
   la bandeja de gestión. Todo sale de tokens: el modo oscuro y el de alto
   contraste ya están resueltos en `tokens.css`. */

/* El botón del encabezado, con el número de respuestas sin leer encima.

   Es el único del grupo con una palabra y con color: entre cuatro cuadrados
   grises de 34px un salvavidas más se perdía. Va primero, separado de los de
   configuración, y con el tinte del avatar para no sumar un color nuevo al
   encabezado. Con las dos clases porque `.volver-al-hub` vive en `style.css`,
   que carga después de esta hoja y fija 34×34 y el gris: acá el ancho lo da
   el texto. */
.volver-al-hub.boton-ayuda {
    position: relative;
    width: auto;
    gap: 6px;
    padding: 0 10px;
    font-size: var(--text-sm);
    font-weight: var(--weight-semibold);
    color: var(--accent-fg);
    background: var(--accent-bg);
    border-color: transparent;
    text-decoration: none;
}

.volver-al-hub.boton-ayuda:hover {
    color: var(--accent-fg);
    border-color: var(--accent);
}

.volver-al-hub.boton-ayuda.con-novedades {
    border-color: var(--accent);
}

/* En alto contraste el tinte es negro sobre negro: el borde lo dibuja. */
[data-theme="contrast"] .volver-al-hub.boton-ayuda {
    border-color: var(--accent);
}

/* El número de respuestas sin leer, adentro del botón y a la derecha de
   «Ayuda». Estuvo colgado de la esquina, encimado al borde, y se veía de
   obra. Es una pastilla del acento con dos movimientos suaves: un latido de
   la pastilla y un halo que nace de su borde, se expande y se apaga, cada
   dos segundos y medio. Sólo cuando la persona no pidió menos movimiento; si
   lo pidió, queda quieta, que ya alcanza. `hidden` sigue mandando: el reset
   lo pone con `!important`, por encima de este `display`. */
.insignia-sin-leer {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 18px;
    height: 18px;
    padding: 0 5px;
    border-radius: 9px;
    background: var(--accent);
    color: var(--text-inverse);
    font-size: 11px;
    font-weight: var(--weight-bold);
    line-height: 1;
}

.insignia-sin-leer::before {
    content: "";
    position: absolute;
    inset: 0;
    border-radius: inherit;
    border: 2px solid var(--accent);
    opacity: 0;
    pointer-events: none;
}

@media (prefers-reduced-motion: no-preference) {
    .insignia-sin-leer {
        animation: latido 2.5s ease-in-out infinite;
    }

    .insignia-sin-leer::before {
        animation: halo 2.5s ease-out infinite;
    }
}

@keyframes latido {
    0%, 100% { transform: scale(1); }
    10% { transform: scale(1.2); }
    22% { transform: scale(1); }
}

@keyframes halo {
    0% { transform: scale(1); opacity: .6; }
    45%, 100% { transform: scale(2.1); opacity: 0; }
}

/* Sobre el violeta claro del acento oscuro, el texto blanco no llega a AA:
   la misma corrección que `.boton-guardar`. */
[data-theme="dark"] .insignia-sin-leer,
[data-theme="contrast"] .insignia-sin-leer {
    color: var(--bg);
}

@media (prefers-color-scheme: dark) {
    html:not([data-theme="light"]):not([data-theme="contrast"]) .insignia-sin-leer {
        color: var(--bg);
    }
}

/* El modal, un poco más ancho que los informativos: el formulario tiene
   tres tarjetas en fila y el hilo, capturas. */
.modal-consultas {
    width: min(640px, calc(100vw - 2 * var(--space-5)));
}

.consultas-icono {
    width: var(--alto-control);
    height: var(--alto-control);
    flex: none;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: var(--radius-lg);
    background: var(--accent-bg);
    color: var(--accent-fg);
}

.consultas-icono .ti {
    width: 20px;
    height: 20px;
}

.consultas-subtitulo {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    margin: 0 0 var(--space-3);
    font-size: var(--text-lg);
    font-weight: var(--weight-semibold);
}

/* ── Las pestañas del panel ──────────────────────────────────
   La misma pieza que `.nav-gestion`: una barra hundida con la activa en
   relieve. Nace `hidden` y la muestra el JavaScript. */
.pestanias {
    display: inline-flex;
    gap: 2px;
    margin: 0 0 var(--space-4);
    padding: 2px;
    background: var(--surface-sunken);
    border: 1px solid var(--border-default);
    border-radius: var(--radius-lg);
}

.pestanias button {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    height: calc(var(--alto-control) - 6px);
    padding: 0 var(--space-3);
    font: inherit;
    font-size: var(--text-sm);
    font-weight: var(--weight-semibold);
    color: var(--text-muted);
    background: transparent;
    border: 0;
    border-radius: var(--radius-md);
    cursor: pointer;
}

.pestanias button[aria-selected="true"] {
    color: var(--accent-fg);
    background: var(--surface-card);
    box-shadow: var(--shadow-xs);
}

.pestanias .ti {
    width: 15px;
    height: 15px;
}

/* ── El formulario ───────────────────────────────────────────── */
.formulario-consulta {
    max-width: none;
    margin: 0;
    gap: var(--space-4);
}

.formulario-consulta.responder {
    margin-top: var(--space-2);
}

/* El botón lleva ícono, y `.boton-guardar` no está pensado para eso: sin
   esto el ícono se pega a la primera letra. */
.formulario-consulta .boton-guardar {
    display: inline-flex;
    align-items: center;
    gap: 6px;
}

/* El aviso de «recibimos tu consulta» va pegado al hilo, no con el margen de
   los mensajes de gestión, que separan secciones enteras. */
.aviso-hilo {
    margin-bottom: var(--space-4);
}

/* Los tres tipos, como tarjetas. El radio queda visible en la esquina
   —además del resalte— para el navegador que no entienda `:has()`. */
.tipos-consulta {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: var(--space-2);
    margin: 0;
    padding: 0;
    border: 0;
}

.tipo-consulta {
    position: relative;
    display: flex;
    flex-direction: column;
    gap: 4px;
    padding: 10px 34px 10px 12px;
    border: 1px solid var(--border-default);
    border-radius: var(--radius-lg);
    background: var(--surface-card);
    cursor: pointer;
}

.tipo-consulta input {
    position: absolute;
    top: 11px;
    right: 11px;
    margin: 0;
    accent-color: var(--accent);
}

.tipo-consulta:has(input:checked) {
    border-color: var(--accent);
    box-shadow: 0 0 0 3px var(--accent-bg);
}

.tipo-consulta:has(input:focus-visible) {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

.tipo-consulta-titulo {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: var(--text-base);
    font-weight: var(--weight-semibold);
}

.tipo-consulta-detalle {
    font-size: var(--text-sm);
    line-height: 1.35;
    color: var(--text-muted);
}

/* ── Las capturas ───────────────────────────────────────────── */
.capturas {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    padding: var(--space-3);
    border: 1px dashed var(--border-default);
    border-radius: var(--radius-lg);
    background: var(--surface-sunken);
}

.capturas.arrastrando {
    border-color: var(--accent);
    background: var(--accent-bg);
}

.capturas-elegir {
    position: relative;
    display: flex;
    align-items: flex-start;
    gap: var(--space-2);
    font-size: var(--text-sm);
    line-height: 1.45;
    color: var(--text-muted);
    cursor: pointer;
}

.capturas-elegir .ti {
    flex: none;
    width: 18px;
    height: 18px;
    margin-top: 1px;
}

/* El campo de archivo se esconde, no se saca: la etiqueta lo abre con un
   clic y sin JavaScript sigue siendo lo que manda los archivos. Enfocado con
   el teclado, el anillo se dibuja sobre la etiqueta entera. */
.capturas-elegir input {
    position: absolute;
    width: 1px;
    height: 1px;
    opacity: 0;
    pointer-events: none;
}

.capturas-elegir:focus-within {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
    border-radius: var(--radius-md);
}

.capturas-aviso {
    margin: 0;
    color: var(--aviso-mant-fg);
}

/* La zona plegada de responder y cerrar: una línea gris, sin recuadro ni
   límites, hasta que se arrastra algo encima o ya hay miniaturas. Ver la
   macro `zona_de_capturas`. */
.capturas.compacta:not(.arrastrando):not(.con-capturas) {
    padding: 0;
    border-color: transparent;
    background: transparent;
}

.capturas.compacta:not(.arrastrando):not(.con-capturas) .capturas-limites {
    display: none;
}

.miniaturas {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    margin: 0;
    padding: 0;
    list-style: none;
}

.miniaturas:empty {
    display: none;
}

.miniaturas li {
    position: relative;
    width: 128px;
    font-size: 11.5px;
    line-height: 1.3;
    color: var(--text-muted);
    overflow-wrap: anywhere;
}

.miniaturas img {
    display: block;
    width: 100%;
    height: 72px;
    object-fit: cover;
    border: 1px solid var(--border-default);
    border-radius: var(--radius-md);
    background: var(--surface-card);
}

.miniatura-quitar {
    position: absolute;
    top: 4px;
    right: 4px;
    width: 20px;
    height: 20px;
    padding: 0;
    border: 0;
    border-radius: 50%;
    font: inherit;
    line-height: 1;
    color: var(--bg);
    background: var(--text-primary);
    cursor: pointer;
}

/* ── Qué se envía ───────────────────────────────────────────── */
.contexto-consulta {
    padding: var(--space-2) var(--space-3);
    border: 1px solid var(--border-default);
    border-radius: var(--radius-lg);
    font-size: var(--text-sm);
}

.contexto-consulta summary {
    display: flex;
    align-items: center;
    gap: 6px;
    font-weight: var(--weight-semibold);
    color: var(--text-secondary);
    cursor: pointer;
}

.contexto-consulta summary .ti {
    width: 14px;
    height: 14px;
    transition: transform var(--transition-fast);
}

.contexto-consulta[open] summary .ti {
    transform: rotate(90deg);
}

.contexto-lista {
    display: grid;
    grid-template-columns: max-content minmax(0, 1fr);
    gap: 4px 14px;
    margin: var(--space-2) 0 0;
}

.contexto-lista dt {
    color: var(--text-muted);
}

.contexto-lista dd {
    margin: 0;
    overflow-wrap: anywhere;
}

/* ── «Mis consultas» ─────────────────────────────────────────── */
.consultas-lista {
    margin-bottom: var(--space-5);
}

.lista-consultas {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    margin: 0;
    padding: 0;
    list-style: none;
}

.consulta-fila {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto;
    gap: 2px 10px;
    align-items: center;
    padding: 10px 12px;
    border: 1px solid var(--border-default);
    border-radius: var(--radius-lg);
    background: var(--surface-card);
    color: var(--text-primary);
    text-decoration: none;
}

.consulta-fila:hover {
    border-color: var(--accent);
}

.consulta-fila.con-novedades {
    border-color: var(--accent-bg);
    background: var(--accent-bg);
}

/* Sobre la fila teñida, la pastilla «Respondida» —del mismo color— se
   perdía: queda sobre fondo de tarjeta. */
.consulta-fila.con-novedades .estado-respondida {
    background: var(--surface-card);
}

.consulta-tipo {
    display: flex;
    grid-row: span 2;
    color: var(--text-muted);
}

.consulta-asunto {
    font-size: var(--text-base);
    font-weight: var(--weight-semibold);
}

.consulta-meta {
    grid-column: 2;
    font-size: var(--text-sm);
    color: var(--text-muted);
}

.punto-novedad {
    display: inline-block;
    width: 8px;
    height: 8px;
    margin-left: 6px;
    border-radius: 50%;
    background: var(--accent);
    vertical-align: middle;
}

/* ── El estado, como pastilla ────────────────────────────────
   De qué lado está la pelota, en tres colores que ya tienen dueño: el
   naranja de la nota de acceso para lo que espera a gestión, el acento para
   lo que espera a la persona, y el verde de las novedades para lo cerrado. */
.estado-consulta {
    display: inline-flex;
    align-items: center;
    grid-row: span 2;
    padding: 2px 9px;
    border-radius: var(--radius-full);
    font-size: var(--text-sm);
    font-weight: var(--weight-semibold);
    white-space: nowrap;
}

.estado-abierta {
    background: var(--nota-acceso-bg);
    color: var(--nota-acceso-fg);
}

.estado-respondida {
    background: var(--accent-bg);
    color: var(--accent-fg);
}

.estado-cerrada {
    background: var(--aviso-nov-bg);
    color: var(--aviso-nov-fg);
}

/* ── El hilo ────────────────────────────────────────────────── */
.hilo-encabezado {
    align-items: flex-start;
}

/* Fijo arriba del modal mientras el hilo scrollea: el título, el estado y el
   botón de cerrar no se van con los mensajes. Lo decidió Diego después de ver
   un hilo largo. `top: 0` mide contra el `<dialog>`, que es lo que scrollea;
   la página `/consultas/{id}` no lo usa porque ahí scrollea la ventana y el
   encabezado es el del sitio.

   **El relleno superior del modal pasa al encabezado, y no al revés.** La
   primera versión dejaba el relleno en el `<dialog>` y subía el encabezado con
   un margen negativo: un elemento `sticky` no puede salirse de la caja de
   contenido de su contenedor, así que el navegador lo devolvía 20px abajo y
   tapaba 8px del primer mensaje que ningún scroll alcanzaba a mostrar
   —medido con Playwright—. Con el relleno acá el encabezado ya nace en el
   borde y no hay nada que corregir. Por eso el panel comparte la regla del
   relleno: es el mismo `<dialog>`, y `modales.js` reemplaza lo de adentro sin
   tocar sus clases. */
.modal-consultas {
    padding-top: 0;
}

.modal-consultas .modal-encabezado {
    padding-top: var(--space-5);
}

.modal-consultas .hilo-encabezado {
    position: sticky;
    top: 0;
    z-index: 2;
    margin-bottom: var(--space-3);
    padding-bottom: var(--space-3);
    background: var(--surface-card);
    border-bottom: 1px solid var(--border-default);
}

.hilo-identidad {
    flex: 1 1 auto;
    min-width: 0;
}

.hilo-identidad h2 {
    font-size: var(--text-lg);
    line-height: 1.3;
}

.consulta-encabezado-meta {
    margin: 2px 0 0;
    font-size: var(--text-sm);
    color: var(--text-muted);
}

.modal-volver {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: var(--alto-control);
    height: var(--alto-control);
    flex: none;
    color: var(--text-muted);
    border: 1px solid var(--border-default);
    border-radius: var(--radius-lg);
}

.modal-volver:hover {
    color: var(--text-primary);
    border-color: var(--accent);
}

.hilo {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
    margin: 0 0 var(--space-4);
}

.mensaje-hilo {
    display: grid;
    grid-template-columns: 30px minmax(0, 1fr);
    gap: var(--space-2);
    align-items: start;
}

.mensaje-hilo .avatar-mini {
    margin: 0;
}

.mensaje-cuerpo {
    padding: 10px 12px;
    border: 1px solid var(--border-default);
    border-radius: var(--radius-lg);
    background: var(--surface-card);
}

/* Lo que escribe gestión se distingue por fondo y por rótulo, no sólo por
   el nombre: quien lee tiene que saber de qué lado viene sin conocer a nadie. */
.mensaje-hilo.de-gestion .mensaje-cuerpo {
    background: var(--accent-bg);
    border-color: transparent;
}

.mensaje-quien {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--space-2);
    margin-bottom: 4px;
    font-size: var(--text-sm);
    color: var(--text-muted);
}

.mensaje-quien b {
    font-size: var(--text-base);
    font-weight: var(--weight-semibold);
    color: var(--text-primary);
}

/* Es el cargo del directorio —«Director Operativo del SIAC»—, o sea una
   frase y no una sigla: sin mayúsculas ni espaciado. */
.rotulo-gestion {
    padding: 1px 7px;
    border: 1px solid var(--accent-bg);
    border-radius: 4px;
    font-size: 11.5px;
    font-weight: var(--weight-semibold);
    color: var(--accent-fg);
    background: var(--surface-card);
}

.mensaje-texto p {
    margin: 0 0 6px;
    font-size: var(--text-md);
    line-height: 1.5;
}

.mensaje-texto p:last-child {
    margin-bottom: 0;
}

.mensaje-texto a {
    color: var(--accent);
    overflow-wrap: anywhere;
}

.adjuntos {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    margin: var(--space-2) 0 0;
    padding: 0;
    list-style: none;
}

.adjunto {
    display: block;
    width: 160px;
    font-size: 11.5px;
    line-height: 1.3;
    color: var(--text-muted);
    text-decoration: none;
    overflow-wrap: anywhere;
}

.adjunto img {
    display: block;
    width: 100%;
    height: 100px;
    object-fit: cover;
    border: 1px solid var(--border-default);
    border-radius: var(--radius-md);
    background: var(--surface-card);
}

.adjunto:hover img {
    border-color: var(--accent);
}

/* La resolución, como bloque aparte: es lo que después se va a buscar. */
.resolucion {
    padding: 10px 12px;
    border-left: 3px solid var(--aviso-nov-fg);
    border-radius: 0 var(--radius-lg) var(--radius-lg) 0;
    background: var(--aviso-nov-bg);
    font-size: var(--text-base);
    color: var(--text-primary);
}

.resolucion b {
    display: block;
    margin: 6px 0 2px;
    font-size: 11px;
    font-weight: var(--weight-bold);
    letter-spacing: .05em;
    text-transform: uppercase;
    color: var(--aviso-nov-fg);
}

.resolucion b:first-child {
    margin-top: 0;
}

.resolucion p {
    display: flex;
    align-items: flex-start;
    gap: 6px;
    margin: 0;
    line-height: 1.45;
}

.resolucion-nota {
    margin-top: 6px;
    font-size: var(--text-sm);
    color: var(--text-muted);
}

.resolucion .resolucion-cuando {
    margin-bottom: 6px;
    font-size: var(--text-sm);
    font-weight: var(--weight-semibold);
    color: var(--aviso-nov-fg);
}

.resolucion-cuando .ti {
    flex: none;
    width: 16px;
    height: 16px;
}

/* En alto contraste todos los fondos son negro: lo que se distinguía por
   fondo necesita un borde para seguir siendo un bloque, y la pastilla, un
   contorno para seguir siendo pastilla. */
[data-theme="contrast"] .mensaje-hilo.de-gestion .mensaje-cuerpo,
[data-theme="contrast"] .consulta-fila.con-novedades {
    border-color: var(--accent);
}

[data-theme="contrast"] .estado-consulta,
[data-theme="contrast"] .rotulo-gestion {
    border: 1px solid currentcolor;
}

[data-theme="contrast"] .pestanias button[aria-selected="true"] {
    outline: 1px solid var(--accent);
    outline-offset: -1px;
}

[data-theme="contrast"] .resolucion {
    border: 1px solid var(--border-default);
    border-left: 3px solid var(--aviso-nov-fg);
}

/* ── La página ──────────────────────────────────────────────── */
.pagina-consultas {
    max-width: 680px;
}

.pagina-consultas .intro-seccion a {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-weight: var(--weight-semibold);
    color: var(--accent);
    text-decoration: none;
}

.boton-cancelar-modal {
    font: inherit;
    background: transparent;
    border: 0;
    padding: 0;
    cursor: pointer;
}

@media (max-width: 560px) {
    .tipos-consulta {
        grid-template-columns: 1fr;
    }
}

/* ── La biblioteca en gestión ──────────────────────────────── */
/* Quién subió cada versión, debajo de la fecha y no pegado a ella. */
.tabla-versiones .celda-detalle .celda-detalle {
    display: block;
}

/* Lo que depende de afuera, en la ficha del documento. Acá y no en
   `biblioteca.css`: las pantallas de gestión no cargan esa hoja. */
.advertencias-documento summary {
    cursor: pointer;
    color: var(--nota-acceso-fg);
    font-size: var(--text-sm);
}

.advertencias-documento ul {
    margin: 6px 0 0;
    padding-left: 18px;
    font-size: var(--text-sm);
    color: var(--text-muted);
}

/* ── La bandeja y el detalle en gestión ────────────────────── */

/* El interruptor de la función, arriba de la bandeja. Encendido es una fila
   discreta; apagado se enmarca con el naranja de las notas de acceso, porque
   es el estado en que la gente no encuentra el botón y hay que verlo. */
.interruptor-consultas {
    padding: 10px 14px;
    border: 1px solid var(--border-default);
    border-radius: var(--radius-lg);
    background: var(--surface-card);
}

.interruptor-consultas .interruptor-estado {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    flex: 1 1 260px;
    font-size: var(--text-sm);
    color: var(--text-muted);
}

.interruptor-consultas .interruptor-estado .ti {
    flex: none;
    width: 16px;
    height: 16px;
}

.interruptor-consultas.apagadas {
    border-color: var(--nota-acceso-fg);
    background: var(--nota-acceso-bg);
}

.interruptor-consultas.apagadas .interruptor-estado {
    color: var(--nota-acceso-fg);
    font-weight: var(--weight-semibold);
}
.tabla-consultas .celda-tipo {
    width: 24px;
    padding-right: 0;
    color: var(--text-muted);
    white-space: nowrap;
}

.tabla-consultas .celda-tipo .ti {
    width: 16px;
    height: 16px;
    vertical-align: middle;
}

.tabla-consultas .tipo-problema { color: var(--nota-acceso-fg); }
.tabla-consultas .tipo-sugerencia { color: var(--bi-fg); }
.tabla-consultas .tipo-pregunta { color: var(--accent); }

.tabla-consultas .celda-tipo .punto-novedad {
    margin: 0 4px 0 0;
}

/* La fila con algo que nadie de gestión leyó: el nombre en negrita, como una
   bandeja de correo. */
.fila-con-novedades .celda-principal a {
    font-weight: var(--weight-bold);
}

/* Lo secundario de cada celda en su propio renglón: el número y el tipo bajo
   el asunto, el correo bajo el nombre, quién escribió bajo la fecha. En línea
   se leían pegados —«Persona de Pruebaprueba@uai.edu.ar»—. */
.tabla-consultas .celda-principal .celda-detalle,
.tabla-consultas .persona-texto .celda-detalle,
.tabla-consultas .celda-movimiento .celda-detalle {
    display: block;
}

/* El asunto es texto libre y se parte: el `nowrap` de las celdas de gestión
   está para fechas y correos, y acá dejaba la tabla en 475px dentro de un
   marco de 358, con el estado detrás del scroll. */
.tabla-consultas .celda-principal {
    white-space: normal;
    min-width: 150px;
}

/* En un teléfono la bandeja se queda con lo que decide a qué entrar: asunto,
   persona y estado. Las otras tres columnas están en el detalle, y con ellas
   en la tabla el estado quedaba fuera de la pantalla, detrás del scroll
   horizontal del marco. */
@media (max-width: 860px) {
    .tabla-consultas .col-secundaria {
        display: none;
    }
}

/* Y en uno angosto, sólo el avatar: con el nombre al lado la tabla no entra
   ni al mínimo y el estado vuelve a irse detrás del scroll. Las iniciales
   siguen diciendo quién, y el nombre está a un toque, en el detalle. */
@media (max-width: 520px) {
    .tabla-consultas .persona-texto {
        display: none;
    }
}

.volver-a-la-bandeja {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-weight: var(--weight-semibold);
    color: var(--accent);
    text-decoration: none;
}

.consulta-detalle {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 300px;
    gap: var(--space-6);
    align-items: start;
}

.consulta-titulo {
    align-items: flex-start;
    flex-wrap: wrap;
}

.consulta-titulo h2 {
    flex: 1 1 auto;
    min-width: 0;
}

.consulta-hilo .hilo {
    margin-top: var(--space-4);
}

.cierre-consulta {
    margin-top: var(--space-5);
}

.panel-contexto {
    margin-bottom: var(--space-4);
    padding: var(--space-4);
    border: 1px solid var(--border-default);
    border-radius: var(--radius-xl);
    background: var(--surface-card);
}

.panel-contexto h3 {
    margin: 0 0 var(--space-3);
    font-size: 11.5px;
    font-weight: var(--weight-semibold);
    letter-spacing: .06em;
    text-transform: uppercase;
    color: var(--text-muted);
}

.panel-contexto .contexto-lista {
    margin: 0;
}

.persona-en-contexto {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    margin: 0 0 var(--space-3);
}

.persona-en-contexto .persona-texto {
    display: flex;
    flex-direction: column;
    min-width: 0;
}

.aperturas-recientes {
    margin: 0;
    padding: 0;
    list-style: none;
    font-size: var(--text-sm);
}

.aperturas-recientes li {
    display: flex;
    justify-content: space-between;
    gap: var(--space-2);
    padding: 5px 0;
    border-bottom: 1px solid var(--border-default);
}

.aperturas-recientes li:last-child {
    border-bottom: 0;
}

.aperturas-recientes li > span:first-child {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    min-width: 0;
}

.aperturas-recientes .ti {
    width: 14px;
    height: 14px;
    color: var(--nota-acceso-fg);
}

@media (max-width: 860px) {
    .consulta-detalle {
        grid-template-columns: 1fr;
    }
}
