/* =========================================================================
   IPUE-PL · Component Kit (.app-*)
   -------------------------------------------------------------------------
   Clases reutilizables que consumen tokens.css.
   Convención de naming:
     .app-<bloque>             → componente
     .app-<bloque>-<elemento>  → parte interna
     .app-<bloque>--<modif>    → modificador (variante)
   Reglas:
     · No hardcodear colores ni tamaños — usar siempre var(--token).
     · No usar !important salvo emergencia documentada.
     · No definir CSS dentro de <style> en vistas .html — extender este
       archivo con un nuevo componente o modificador.
   ========================================================================= */

/* ═════════════════════════════════════════════════════════════════════════
   0.  GLOBALES DE ACCESIBILIDAD
   ═════════════════════════════════════════════════════════════════════════ */

/* Coloreado de controles nativos (checkbox/radio/range/progress). */
:root {
    accent-color: var(--color-action-fg);
}

/* Selección de texto en colores de marca. ::selection no es heredable así que
   se declara una vez en :root y aplica a todo el árbol. */
::selection {
    background: var(--selection-bg);
    color: var(--selection-fg);
}

/* Focus ring visible solo cuando el foco viene del teclado.
   :where() mantiene la especificidad en 0 para que sea fácil de sobreescribir. */
:where(a:any-link, button, [role="button"], .app-btn, .app-btn-icon, .app-btn--icon,
       .app-th-button, .app-year-nav-btn, .app-paginador-btns button,
       input, select, textarea, [tabindex]):focus-visible {
    outline: 3px solid var(--ring-focus);
    outline-offset: 2px;
    border-radius: var(--radius-md);
}

/* Skip-link: oculto hasta recibir foco. Cuando aparece, va al top-left bien visible. */
.app-skip-link:focus,
.app-skip-link:focus-visible {
    position: fixed;
    top: var(--sp-2);
    left: var(--sp-2);
    z-index: var(--z-toast);
    background: var(--color-action);
    color: var(--text-on-action);
    padding: var(--sp-2) var(--sp-3);
    border-radius: var(--radius-md);
    font-weight: var(--fw-semibold);
    text-decoration: none;
    box-shadow: var(--shadow-elevated);
}

/* <main> focalizable (target del skip-link). Sin halo cuando recibe foco programático. */
.app-main {
    overflow: hidden;
    outline: none;
}

/* Texto equilibrado en titulares cortos (mejora legibilidad sin coste). */
.app-page-title,
.app-card-title,
.app-modal-head .modal-title,
.app-panel-title {
    text-wrap: balance;
}

/* text-wrap: pretty optimiza líneas finales en bloques de texto largos
   (evita huérfanos, mejora densidad). Solo en párrafos descriptivos.  */
.app-page-subtitle,
.app-card-desc,
.app-module-card-desc,
.app-empty,
.app-accordion-title-desc {
    text-wrap: pretty;
}

/* Respeto a usuarios con vestibular sensitivity:
   solo se desactivan animaciones decorativas o infinitas. Las transiciones de
   hover / focus son cortas (<400ms) y se conservan como feedback útil.
   Evitamos el override global de animation-duration porque puede empeorar
   ciertas animaciones (recomendación modern-web-guidance/css §9). */
@media (prefers-reduced-motion: reduce) {
    .app-alert--danger { animation: none; }
    .app-th-button .app-icon--fill { animation: none; }
    html { scroll-behavior: auto; }
}

/* Transiciones suaves entre páginas (cross-document, MPA).
   DESACTIVADAS el 2026-08-04. Medido: al navegar entre páginas de la app con
   `navigation: auto`, el documento destino DEJA DE PRODUCIR FOTOGRAMAS —
   requestAnimationFrame no vuelve a dispararse, `:active-view-transition` se
   queda en true y ni siquiera se puede capturar la pantalla. El DOM sí se
   actualiza (el JS corre y los datos llegan), pero lo pintado se congela.
   Reproducido en Chromium y en Chrome real, con y sin ventana; desaparece al
   forzar prefers-reduced-motion, que es lo que apaga esta regla. No hay
   peticiones de red pendientes, así que no es un recurso que bloquee el render.
   Se deja apagado hasta dar con la causa: el coste es un fundido, el riesgo era
   una interfaz congelada. (modern-web-guidance: cross-document-transitions.)
@view-transition { navigation: auto; }
@media (prefers-reduced-motion: reduce) {
    @view-transition { navigation: none; }
}
*/

/* :user-invalid solo marca un campo como inválido DESPUÉS de que el usuario
   haya interactuado con él (cambio, blur, intento de submit). Evita el patrón
   hostil de Bootstrap (.is-invalid aplicado en page-load).
   Funciona junto a `required`, `type="email"`, `pattern=`, `minlength=`. */
.app-filters input:user-invalid,
.app-filters select:user-invalid,
.modal-body input:user-invalid,
.modal-body select:user-invalid,
.modal-body textarea:user-invalid,
.app-dialog-body input:user-invalid,
.app-dialog-body select:user-invalid,
.app-dialog-body textarea:user-invalid,
.app-action-bar input:user-invalid,
.app-action-bar select:user-invalid {
    border-color: var(--danger);
    box-shadow: 0 0 0 2px color-mix(in oklab, var(--danger) 20%, transparent);
}

/* iOS Safari hace zoom automático cuando un input recibe foco y su font-size
   es < 16px. Subimos a 16px solo en punteros gruesos (coarse = touch).
   Cubre los inputs nativos, los .form-control-sm/.form-select-sm de Bootstrap
   y los inputs de .app-filters. */
@media (pointer: coarse) {
    .app-filters input,
    .app-filters select,
    .app-action-bar input,
    .app-action-bar select,
    .form-control-sm,
    .form-select-sm,
    .form-control,
    .form-select {
        font-size: 16px;
    }
}

/* ═════════════════════════════════════════════════════════════════════════
   1.  PÁGINA "WORKING" (cabecera fija + cuerpo con scroll)
   ═════════════════════════════════════════════════════════════════════════ */
.app-page {
    display: flex;
    flex-direction: column;
    height: 100%;
    overflow: hidden;
    background: var(--page-bg);
    /* Contenedor de consulta por ancho: las variantes internas (.app-page-body--split)
       reaccionan al ancho real de .app-page, no al del viewport.
       Es «nombre / tipo»: hasta el 2026-11-03 estaba al revés, el navegador descartaba
       la declaración y ningún @container app-page se aplicaba. Al ser contenedor es
       también el bloque contenedor de los position: fixed de dentro; hoy no hay
       ninguno (overlay, avisos y tooltip cuelgan de <body> o van en el top-layer). */
    container: app-page / inline-size;
}

.app-page-head {
    flex-shrink: 0;
    background: var(--surface);
    border-bottom: 1px solid var(--border);
    padding: var(--head-pad-y) var(--page-pad-x) 0;
}

.app-page-head-row {
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    gap: var(--sp-2);
    flex-wrap: wrap;
    margin-bottom: var(--sp-1);
}

.app-page-title {
    font-size: var(--fs-lg);
    font-weight: var(--fw-bold);
    color: var(--text-strong);
    margin: 0;
    display: flex;
    align-items: center;
    gap: var(--sp-2);
}

.app-page-title > :is(i, .app-icon) { color: var(--color-action-fg); }

.app-page-subtitle {
    font-size: var(--fs-sm);
    color: var(--text-muted);
    margin: 0.1rem 0 var(--sp-3);
}

/* Antetítulo: dice de QUÉ tipo es la página, para que el h1 pueda decir CUÁL
   es (el nombre de la persona en una ficha, no «Ficha del miembro»). */
.app-page-eyebrow {
    display: flex;
    align-items: center;
    gap: var(--sp-1);
    margin: 0;
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
    text-transform: uppercase;
    letter-spacing: 0.5px;
    color: var(--text-muted);
}

.app-page-eyebrow .app-icon { font-size: 1em; }

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

/* Variante: el cuerpo es un juego de pestañas a todo el ancho (ficha del miembro).
   Hasta el 2026-11-02 era un split con una tarjeta fija a la izquierda. */
.app-page-body--pestanas {
    display: flex;
    padding: var(--page-pad-y) var(--page-pad-x);
}

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

.app-page-main .tab-content {
    flex: 1;
    overflow: hidden;
    min-height: 0;
}

.app-page-main .tab-pane {
    height: 100%;
    overflow-y: auto;
    padding: var(--sp-4) 0;
}

/* Container query: por debajo de 992px de ancho del .app-page, scrollea la página
   entera en vez de solo la pestaña (la cabecera crece al partirse en líneas).
   Reacciona al ancho real de .app-page, no al del viewport. */
/* Nunca se había aplicado hasta el 2026-11-03 (el contenedor estaba mal declarado) y
   le faltaba media regla: quitaba el scroll a la pestaña pero dejaba .tab-content con
   overflow: hidden y altura fija, y el final de la pestaña quedaba cortado y sin poder
   bajar. Ahora el contenido toma su alto natural y el que scrollea es el cuerpo. */
@container app-page (max-inline-size: 991px) {
    .app-page-body--pestanas { overflow-y: auto; align-items: flex-start; }
    .app-page-main  { overflow: visible; }
    .app-page-main .tab-content { overflow: visible; flex: none; }
    .app-page-main .tab-pane { height: auto; overflow-y: visible; }
}

.app-page-section {
    height: 100%;
    overflow-y: auto;
    padding: var(--page-pad-y) var(--page-pad-x);
    box-sizing: border-box;
}

@media (max-width: 575.98px) {
    .app-page-head-row {
        flex-direction: column;
        align-items: stretch;
    }
    .app-page-head-row > div:last-child {
        align-self: flex-end;
    }
    .app-page-title    { font-size: var(--fs-md); }
    .app-page-subtitle { font-size: var(--fs-xs); margin-bottom: var(--sp-2); }
}

/* ═════════════════════════════════════════════════════════════════════════
   2.  CONTEXTO (badge distrito / iglesia)
   ═════════════════════════════════════════════════════════════════════════ */
.app-context {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    background: linear-gradient(135deg, var(--color-action-soft), light-dark(#E8F0FE, #16202E));
    border: 1px solid light-dark(#C9D6F0, #2C3A55);
    border-radius: var(--radius-lg);
    padding: var(--sp-2) var(--sp-3);
    flex-shrink: 0;
}

.app-context-item {
    display: flex;
    align-items: center;
    gap: var(--sp-1);
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    color: var(--color-action-fg);
    white-space: nowrap;
}

.app-context-item :is(i, .app-icon) {
    font-size: 0.72rem;
    color: var(--ipue-amarillo);
}

.app-context-sep {
    width: 1px;
    height: 16px;
    background: var(--border);
}

@media (max-width: 576px) {
    .app-context {
        flex-direction: column;
        align-items: flex-end;
        gap: var(--sp-1);
        padding: var(--sp-1) var(--sp-2);
    }
    .app-context-sep { display: none; }
    .app-context-item { font-size: var(--fs-xs); }
}

/* ═════════════════════════════════════════════════════════════════════════
   3.  TABS (estilo IPUE: underline, hover dorado, active azul)
   ═════════════════════════════════════════════════════════════════════════ */
.app-tabs {
    border-bottom: none;
    gap: var(--sp-1);
    margin-bottom: 0;
}

.app-tabs .nav-link {
    color: var(--gray-600);
    font-weight: var(--fw-medium);
    font-size: var(--fs-sm);
    border: none;
    border-bottom: 2px solid transparent;
    padding: var(--sp-2) var(--sp-3);
    border-radius: 0;
    transition: color var(--tr-fast), border-color var(--tr-fast);
}

.app-tabs .nav-link:hover:not(.active) {
    color: var(--ipue-amarillo) !important;
    border-bottom-color: var(--ipue-amarillo) !important;
    background: transparent !important;
}

.app-tabs .nav-link.active {
    color: var(--color-action-fg) !important;
    font-weight: var(--fw-semibold) !important;
    border-bottom: 2px solid var(--color-action-fg) !important;
    background: transparent !important;
}

/* Cubre tanto la clase de Bootstrap como el atributo nativo del <button>. */
.app-tabs .nav-link:is(.disabled, [disabled]) {
    opacity: 0.45;
    pointer-events: none;
}

/* Marca de un módulo que aún no está construido (ej. pestañas del histórico).
   Acompaña a la pestaña deshabilitada para que se lea como «llegará» y no como
   algo averiado. */
.app-tab-pronto {
    margin-inline-start: var(--sp-2);
    padding: 0 var(--sp-1);
    border-radius: var(--radius-pill);
    background: var(--neutral-soft);
    color: var(--neutral-soft-text);
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
    text-transform: uppercase;
    letter-spacing: .04em;
}

/* Sub-cabecera de sección (ej. agrupación por distrito en libros locales) */
.app-distrito-header {
    background: linear-gradient(135deg, var(--gray-50), var(--gray-100));
    border-radius: var(--radius-lg);
    padding: var(--sp-2) var(--sp-3);
    margin-bottom: var(--sp-2);
    display: flex;
    align-items: center;
    justify-content: space-between;
    font-size: var(--fs-sm);
}

/* Variante anidada (tabs dentro de un tab — ej. catálogos) */
.app-tabs--sub { border-bottom: 2px solid var(--border); }

.app-tabs--sub .nav-link {
    font-size: var(--fs-xs);
    padding: var(--sp-1) var(--sp-3);
    margin-bottom: -2px;
}

/* Panel de una sub-pestaña: respira del borde de la barra sin separarse tanto
   como un panel de primer nivel. */
.app-tab-pane--sub { padding-block: var(--sp-2); }

/* ═════════════════════════════════════════════════════════════════════════
   4.  CARDS (genéricas)
   ═════════════════════════════════════════════════════════════════════════ */
.app-card {
    background: var(--surface-card);
    border-radius: var(--radius-xl);
    box-shadow: var(--shadow-card);
    border: 1px solid var(--border-default);
    padding: var(--sp-4);
    transition: box-shadow var(--tr-base);
}

.app-card--soft {
    box-shadow: var(--shadow-soft);
    padding: var(--sp-3) var(--sp-4);
}

/* Fila de un valor de catálogo (membresía nacional, pestaña Catálogos). */
.app-catalogo-nombre {
    flex: 1;
    font-weight: var(--fw-semibold);
    color: var(--text-strong);
}
.app-catalogo-codigo {
    font-size: var(--fs-xs);
    color: var(--text-muted);
}

.app-card--bordered {
    border-left: 4px solid var(--c, var(--ipue-celeste));
}

.app-card--highlight {
    background: linear-gradient(135deg, var(--color-action-soft), light-dark(#E8F0FE, #16202E));
    border-color: light-dark(#C9D6F0, #2C3A55);
}

.app-card--hoverable {
    cursor: pointer;
}

.app-card--hoverable:hover {
    box-shadow: var(--shadow-card-hover);
}

/* Variante fila: info a la izquierda, acciones a la derecha (rol-card, sesion-card) */
.app-card--row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-3);
    flex-wrap: wrap;
    margin-bottom: var(--sp-2);
}

.app-card-info {
    flex: 1;
    min-width: 160px;
}

.app-card-title {
    font-weight: var(--fw-bold);
    color: var(--gray-800);
    font-size: var(--fs-base);
}

.app-card-desc {
    font-size: var(--fs-sm);
    color: var(--gray-600);
    margin: 0;
}

.app-card-meta {
    font-size: var(--fs-xs);
    color: var(--gray-500);
}

/* Resalte para "mi sesión" / elemento actual del usuario */
.app-card--current {
    border-color: var(--color-action-fg);
    background: var(--color-action-soft);
}

/* ═════════════════════════════════════════════════════════════════════════
   5.  STATS (KPIs grandes y compactos)
   ═════════════════════════════════════════════════════════════════════════ */
.app-stats-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
    gap: var(--sp-3);
    margin-bottom: var(--sp-4);
}

/* Móvil: 2 stats por fila para que quepa info útil sin scroll */
@media (max-width: 575.98px) {
    .app-stats-grid {
        grid-template-columns: repeat(2, 1fr);
        gap: var(--sp-2);
        margin-bottom: var(--sp-3);
    }
    .app-stat {
        padding: var(--sp-2) var(--sp-3);
        flex-direction: row;
        align-items: center;
        gap: var(--sp-2);
    }
    .app-stat-num   { font-size: var(--fs-2xl); }
    .app-stat-label { font-size: var(--fs-xs); }
    .app-stat-icon  { font-size: 1.3rem; }
}

/* Variante grande (vertical, número dominante) */
.app-stat {
    background: var(--surface);
    border-radius: var(--radius-xl);
    border: 1px solid var(--border);
    border-left: 4px solid var(--c, var(--ipue-celeste));
    box-shadow: var(--shadow-sm);
    padding: var(--sp-4) var(--sp-5);
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    gap: var(--sp-3);
}

.app-stat-info { min-width: 0; }

.app-stat-num {
    font-size: var(--fs-3xl);
    font-weight: var(--fw-bold);
    line-height: var(--lh-tight);
    color: var(--c, var(--color-action-fg));
}

.app-stat-label {
    font-size: var(--fs-sm);
    color: var(--gray-600);
    text-transform: uppercase;
    letter-spacing: 0.4px;
    margin: var(--sp-1) 0 0;
}

.app-stat-sub {
    font-size: var(--fs-xs);
    color: var(--gray-500);
    margin: 0.15rem 0 0;
}

.app-stat-icon {
    font-size: 1.6rem;
    color: var(--c, var(--ipue-celeste));
    opacity: 0.65;
    flex-shrink: 0;
}

/* Variante compacta (horizontal, icono lateral).
   justify-content propio: .app-stat reparte con space-between, que en la
   variante grande empuja el icono al borde opuesto del número. Aquí el icono va
   PEGADO a su cifra, y sin esto el resultado dependía del orden de los hijos en
   el DOM — por eso las tarjetas de membresía nacional (icono primero) salían
   espejadas respecto a las del censo local (icono último). */
.app-stat--compact {
    padding: var(--sp-2) var(--sp-3);
    border-left-width: 3px;
    border-radius: var(--radius-lg);
    flex-direction: row;
    align-items: center;
    justify-content: flex-start;
}

.app-stat--compact .app-stat-num   { font-size: var(--fs-lg); }
.app-stat--compact .app-stat-label { font-size: var(--fs-xs); }
.app-stat--compact .app-stat-icon  { font-size: 1.05rem; opacity: 1; }

/* ═════════════════════════════════════════════════════════════════════════
   6.  PANEL (sección con título)
   ═════════════════════════════════════════════════════════════════════════ */
.app-panel {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-xl);
    padding: var(--sp-3) var(--sp-4);
    box-shadow: var(--shadow-sm);
}

.app-panel-title {
    font-size: var(--fs-base);
    font-weight: var(--fw-bold);
    color: var(--gray-800);
    margin: 0 0 var(--sp-3);
    display: flex;
    align-items: center;
    gap: var(--sp-1);
}

.app-panel-title > :is(i, .app-icon) { color: var(--ipue-celeste); }

.app-panel-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
    gap: var(--sp-3);
}

/* Fila key/value dentro de un panel (censo, listas resumen) */
.app-row {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 0.22rem 0;
    font-size: var(--fs-base);
    border-bottom: 1px dashed var(--border-soft);
}

.app-row:last-child { border-bottom: none; }

.app-row-key { color: var(--gray-700); }

.app-row-val {
    font-weight: var(--fw-semibold);
    color: var(--gray-800);
}

/* ═════════════════════════════════════════════════════════════════════════
   7.  TABLA densa
   ═════════════════════════════════════════════════════════════════════════ */
.app-table-wrap {
    /* Ancla de posicionamiento para lo que la tabla lleve dentro en absolute:
       sobre todo los .visually-hidden de los botones de icono (el texto que lee
       el lector de pantalla), que Bootstrap posiciona con absolute. Sin un
       ancestro posicionado se cuelgan del DOCUMENTO: con pocas filas no se nota,
       pero con sesenta el ultimo cae a 4700px y estira la pagina entera, con su
       barra de scroll inutil y un vacio enorme bajo el pie. */
    position: relative;
    overflow-x: auto;
    overflow-y: auto;
    background: var(--surface);
    border-radius: var(--radius-xl);
    border: 1px solid var(--border);
    /* scrollbar-color (thumb / track) y scrollbar-width son las propiedades
       estándar (Baseline 2024). Funcionan en Chrome/Edge/Firefox/Safari sin
       prefijos. Sustituyen al ::-webkit-scrollbar legacy. */
    scrollbar-color: var(--gray-400) var(--surface-soft);
}

/* En móvil, scrollbar más fino para no robar espacio a la tabla. */
@media (max-width: 767.98px) {
    .app-table-wrap { scrollbar-width: thin; }
}

.app-table {
    width: 100%;
    border-collapse: separate;
    border-spacing: 0;
    min-width: 650px;
}

.app-table thead th {
    background: var(--surface-soft);
    font-size: var(--fs-xs);
    text-transform: uppercase;
    color: var(--gray-600);
    font-weight: var(--fw-semibold);
    padding: var(--sp-2) var(--sp-3);
    border-bottom: 2px solid var(--border);
    white-space: nowrap;
    text-align: left;
    position: sticky;
    top: 0;
    z-index: var(--z-sticky);
    /* Elevación sutil para separar la cabecera fija del cuerpo al hacer scroll */
    box-shadow: var(--shadow-sm);
}

.app-table thead th.num { text-align: right; }

/* Header de tabla que es trigger de dropdown / orden (libroDiario) */
.app-th-button {
    background: transparent;
    border: none;
    padding: 0;
    color: inherit;
    font: inherit;
    text-transform: inherit;
    letter-spacing: inherit;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    gap: var(--sp-1);
}

.app-th-button:hover { color: var(--color-action-fg); }
.app-th-button .app-icon--fill { animation: app-filter-pulse 0.3s ease; }

@keyframes app-filter-pulse {
    0%, 100% { transform: scale(1); }
    50%      { transform: scale(1.2); }
}

.app-table tbody td {
    padding: var(--sp-2) var(--sp-3);
    font-size: var(--fs-base);
    border-bottom: 1px solid var(--border-soft);
    vertical-align: middle;
}

.app-table tbody td.num { text-align: right; font-variant-numeric: tabular-nums; }

/* ── Aviso de que la tabla sigue a la derecha ─────────────────────────────
   Una rejilla de doce meses no cabe en pantalla y la barra de scroll queda al
   fondo, donde nadie la mira: sin esto, media tabla es invisible sin saberlo.
   Se difumina el CONTENIDO con una mascara en vez de superponer un degradado,
   que obligaria a acertar el color de fondo y taparia el texto en dark.
   La mascara se apoya en dos variables que el JS mueve segun donde este el
   scroll, para que el borde solo se difumine cuando queda algo por ver. */
.app-table-wrap--fade {
    --fade-fin: 0px;
    -webkit-mask-image: linear-gradient(to right,
        black calc(100% - var(--fade-fin)), transparent 100%);
    mask-image: linear-gradient(to right,
        black calc(100% - var(--fade-fin)), transparent 100%);
}

/* Solo se difumina la DERECHA. Por la izquierda esta la columna del socio, fija:
   difuminarla emborronaba justo el nombre, que es lo unico que no puede perderse
   de vista. Que ahi hay contenido oculto lo dice su sombra. */
.app-table-wrap--desplazada td.socio-col,
.app-table-wrap--desplazada th.socio-col {
    box-shadow: 6px 0 8px -6px light-dark(rgb(0 0 0 / 0.25), rgb(0 0 0 / 0.6));
    clip-path: inset(0 -14px 0 0);
}

/* ── Tabla aireada ────────────────────────────────────────────────────────
   Para rejillas de muchas columnas (socios: 12 meses), donde el padding normal
   de la tabla deja las celdas tan juntas que la vista no encuentra donde
   apoyarse. No se cambia el padding GLOBAL: en una tabla de 6 columnas el
   actual esta bien y subirlo obligaria a scrollear de mas. */
.app-table--aireada thead th,
.app-table--aireada tbody td,
.app-table--aireada tfoot td,
.app-table--aireada tfoot th {
    padding: var(--sp-3) var(--sp-4);
}

/* Altura minima de fila: el aire vertical es el que hace legible una rejilla
   ancha, mas incluso que el horizontal. */
.app-table--aireada tbody tr { block-size: 3.5rem; }

/* Zebra striping: filas pares con superficie suave para escanear la fila en
   una tabla ancha. Va ANTES de hover/disabled/anulada para que esos estados
   (misma especificidad) ganen por orden de cascada. */
.app-table tbody tr:nth-child(even) {
    background: var(--surface-soft);
}

.app-table tbody tr:hover {
    background: var(--surface-soft);
}

.app-table tbody tr.app-table-row--disabled {
    background: var(--gray-200);
    color: var(--gray-500);
}

.app-table tbody tr.app-table-row--disabled td { color: inherit; }

/* Fila anulada (libro diario): fondo neutro, texto desaturado */
.app-table tbody tr.app-table-row--anulada {
    background: var(--gray-200);
}

.app-table tbody tr.app-table-row--anulada:hover { background: var(--gray-300); }

.app-table tbody tr.app-table-row--anulada td,
.app-table tbody tr.app-table-row--anulada td span:not(.app-badge),
.app-table tbody tr.app-table-row--anulada td .fw-bold,
.app-table tbody tr.app-table-row--anulada td .fw-semibold {
    color: var(--gray-400);
}

/* Variante inline (dentro de un panel — sin wrap, sin sticky, fonts menores) */
.app-table--inline { min-width: 0; }
.app-table--inline thead th {
    background: var(--surface-soft);
    font-size: var(--fs-xs);
    padding: var(--sp-1) var(--sp-2);
    position: static;
}
.app-table--inline tbody td {
    padding: var(--sp-1) var(--sp-2);
    font-size: var(--fs-sm);
}

/* Variante compacta (libroDiario): equilibrio entre densidad y comodidad de
   lectura. Cuerpo a --fs-sm (12.5px: mayor que la cabecera de 11px pero menor
   que el 14px base) con interlineado holgado para reducir la fatiga visual en
   una tabla muy ancha. Mayor especificidad que .app-cell--sm, así que Método y
   Acumulado también se igualan. */
.app-table--compact tbody td {
    font-size: var(--fs-sm);
    line-height: var(--lh-base);
}

/* ── Rejilla de socios misioneros ─────────────────────────────────────────
   Cada celda de mes es un objetivo táctil grande: el <label> envuelve el
   checkbox y llena toda la celda, así se marca pulsando en cualquier punto
   (no solo sobre el diminuto checkbox). Estado marcado teñido con :has(). */

/* Columna "Socio" fija al hacer scroll horizontal en la rejilla ancha. */
.app-table th.socio-col { left: 0; z-index: calc(var(--z-sticky) + 1); }
.app-table td.socio-col {
    position: sticky;
    left: 0;
    z-index: 1;
    background: var(--surface);
}
/* Separador para que la columna congelada se lea como panel fijo. */
.app-table th.socio-col,
.app-table td.socio-col { border-inline-end: 1px solid var(--border); }
.app-table tbody tr:nth-child(even) td.socio-col { background: var(--surface-soft); }
.app-table tbody tr:hover td.socio-col { background: var(--surface-soft); }

/* La celda no lleva padding: el label ocupa el 100% para maximizar el área.
   Gana ancho minimo propio para que las 12 columnas no se estrujen entre si. */
td.socio-mes-cell {
    padding: 0;
    min-inline-size: 3rem;
}

/* Los 12 meses se leen por trimestres: una divisoria cada tres columnas da a la
   vista donde apoyarse para contar, en vez de doce casillas identicas seguidas. */
.socio-mes-cell--trimestre,
.app-table thead th.socio-mes-cell--trimestre {
    border-inline-start: 1px solid var(--border);
}

.socio-mes {
    display: flex;
    align-items: center;
    justify-content: center;
    min-block-size: 48px;          /* objetivo táctil cómodo (WCAG 2.5.x) */
    margin: 0;
    cursor: pointer;
    transition: background-color .12s ease;
}
/* Solo al pasar por encima se tiñe, para que se vea que la celda entera es
   clicable. El mes MARCADO no lleva fondo: la casilla ya se pone azul y basta.
   Teñir la celda entera dibujaba un recuadro oscuro alrededor de cada casilla y
   la rejilla se llenaba de bloques; el objetivo era acertar mejor con el dedo,
   no que se viera peor. */
.socio-mes:hover {
    background: color-mix(in oklab, var(--color-action-fg) 10%, transparent);
}
.socio-mes:has(input:focus-visible) {
    outline: 2px solid var(--ring-focus);
    outline-offset: -3px;
}
.socio-mes input[type="checkbox"] {
    inline-size: 1.4rem;
    block-size: 1.4rem;
    margin: 0;
    cursor: pointer;
    /* accent-color de marca ya es global */
}

/* Punteros gruesos (táctil): objetivo y checkbox aún mayores. */
@media (pointer: coarse) {
    .socio-mes { min-block-size: 52px; }
    .socio-mes input[type="checkbox"] { inline-size: 1.6rem; block-size: 1.6rem; }
}

/* Contador "N/12" de meses aportados por socio. */
.socio-aportes {
    font-variant-numeric: tabular-nums;
    font-weight: var(--fw-semibold);
    color: var(--color-action-fg);
    white-space: nowrap;
}
.socio-aportes small { color: var(--gray-500); }

/* Variante de solo lectura de la celda de mes (histórico de ejercicios cerrados).
   Conserva la tinta y la altura de la rejilla en vivo, pero sin checkbox: un libro
   cerrado no se modifica, así que no hay ningún control que ofrecer. */
.socio-mes-ro {
    display: flex;
    align-items: center;
    justify-content: center;
    min-block-size: 44px;
    color: var(--color-action-fg);
}

/* En el historico la senal es el icono de check, por el mismo motivo que en la
   rejilla en vivo: el fondo teñido convertia la tabla en un tablero de bloques. */
.socio-mes-ro--si {
    font-weight: var(--fw-semibold);
}

/* Contacto: correo y telefono apilados en una sola columna. La rejilla ya tiene
   12 meses; una columna por dato la haria impracticable.
   El apilado se hace con display:block en el telefono y NO con flex en la celda:
   un <td> en flex deja de ser table-cell, pierde la altura de la fila y pinta su
   borde inferior a media celda, cruzando el texto. */
.socio-contacto { line-height: 1.25; }
.socio-contacto small { display: block; }

/* Los dos botones de la fila, en linea. La columna es estrecha porque los meses
   se llevan el ancho, y sin esto se apilaban uno encima de otro. nowrap y no
   flex: un <td> en flex deja de ser table-cell y descoloca el borde de la fila. */
.app-table-acciones {
    white-space: nowrap;
}
.app-table-acciones > .app-btn + .app-btn {
    margin-inline-start: var(--sp-1);
}

/* Los textos de la rejilla no se parten NI se estiran sin limite: con tantas
   columnas, un correo largo empujaba los meses fuera de la pantalla. Cada una
   tiene su techo y lo que sobra se recorta con puntos suspensivos; el valor
   entero sigue disponible en el title de la celda. */
.socio-ciudad,
.socio-lider,
.socio-contacto,
.app-table th.socio-col,
.app-table td.socio-col {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.app-table td.socio-col { max-inline-size: 18rem; }
.socio-contacto         { max-inline-size: 11rem; }

/* Quien lleva al socio, bajo su nombre. Tenia columna propia y por si sola
   empujaba los meses fuera de la pantalla; aqui dice lo mismo sin gastar ancho.
   El icono lo distingue de la ciudad, que va en la misma linea. */
.socio-lider {
    display: inline-flex;
    align-items: center;
    gap: 0.15rem;
    min-inline-size: 0;
}

/* La ciudad tiene columna propia; en la ficha del socio solo queda el badge y
   quien lo lleva, que es lo que puede recortarse si la celda se queda corta. */
.socio-identidad .socio-lider { flex-shrink: 1; }

/* Columna de seleccion para el traspaso en bloque. Ancho fijo y pegada a la
   izquierda, delante de la del socio: las dos viajan juntas al scrollear, asi
   que la casilla nunca se separa de la persona a la que pertenece. */
.app-table th.socio-check-col,
.app-table td.socio-check-col {
    inline-size: 2.75rem;
    padding-inline: var(--sp-3);
    text-align: center;
    position: sticky;
    left: 0;
    background: var(--surface);
}
.app-table th.socio-check-col { z-index: calc(var(--z-sticky) + 1); }
.app-table td.socio-check-col { z-index: 1; }
.app-table tbody tr:nth-child(even) td.socio-check-col { background: var(--surface-soft); }
.app-table tbody tr:hover td.socio-check-col { background: var(--surface-soft); }

/* La fila de totales tambien lleva columnas fijas. El <th> del pie no hereda el
   position:sticky del <thead>, asi que sin esto scrolleaba con el contenido y se
   veia pasar el texto de las otras columnas por encima. */
.app-table tfoot th.socio-col,
.app-table tfoot td.socio-check-col {
    position: sticky;
    z-index: 1;
    background: var(--surface);
}

/* Con la columna de seleccion delante, la del socio se pega justo detras. El
   desplazamiento se aplica por clase en la TABLA y no siempre, porque a quien no
   puede repartir cartera esa columna no se le pinta y el socio manda en el
   borde.
   El offset lo MIDE el JS en vez de darlo aqui fijo: el ancho real de una celda
   de tabla lo decide el contenido, no inline-size, y con un valor adivinado
   quedaba una rendija por la que se veia pasar el texto al scrollear. */
.app-table--con-seleccion th.socio-col,
.app-table--con-seleccion td.socio-col { left: var(--offset-socio, 2.75rem); }

.socio-check-col[hidden] { display: none; }

/* La celda del socio dice QUIEN es en dos lineas: el nombre, y debajo lo que lo
   matiza (fuera de la membresia, de donde es). Antes iba todo en columnas
   separadas y le robaba ancho a una rejilla que ya tiene doce meses. */
.socio-nombre {
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Marca de «fuera de la membresia»: en la mision es la situacion normal de media
   lista, asi que va como icono junto al nombre y no como badge con texto, que se
   repetia en cada fila y se comia el ancho de la rejilla. */
/* Marca de «fuera de la membresía».
   Era el icono suelto en --warning-soft-text (#664D03 en claro): junto a un
   nombre en negrita se leía como texto oscuro cualquiera y nadie se enteraba de
   qué señalaba. Sobre --warning a secas tampoco valía: el ámbar corporativo da
   ~1.9:1 contra blanco. Con fondo suave la mancha se ve de lejos y el icono
   conserva contraste contra ella. */
.socio-marca-externo {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    inline-size: 1.35em;
    block-size: 1.35em;
    border-radius: 50%;
    background: var(--warning-soft);
    color: var(--warning-soft-text);
    margin-inline-start: var(--sp-2);
    vertical-align: text-bottom;
    flex: none;
}

/* Leyenda de la marca, junto al buscador. El pie de la tabla ya la explicaba,
   pero en una rejilla larga queda fuera de pantalla justo cuando hace falta. */
.socio-leyenda {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-1);
    margin: 0;
    color: var(--text-muted);
    font-size: var(--fs-sm);
}

.socio-identidad {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    min-inline-size: 0;
    margin-block-start: 0.15rem;
}

/* El badge no se encoge: es la marca que hay que ver, no el texto que sobra. */
.socio-identidad .app-badge { flex-shrink: 0; }

/* Pie: cuantos socios aportaron cada mes. En blanco cuando son cero, para que
   la vista lea de un golpe que ese mes esta vacio. */
.socio-mes-total {
    font-variant-numeric: tabular-nums;
    color: var(--text-muted);
}
.socio-mes-total--con {
    color: var(--color-action-fg);
    font-weight: var(--fw-semibold);
}

/* Cuota editable dentro de la rejilla: estrecha, a la derecha y con cifras
   tabulares para que los importes de la columna se lean alineados. */
.socio-cuota-input {
    inline-size: 5rem;
    text-align: end;
    font-variant-numeric: tabular-nums;
    margin-inline-start: auto;
}

/* Los importes no se parten: la columna es estrecha y «zł 250,00» acababa en dos
   lineas, con el simbolo arriba y la cifra debajo. */
.socio-aportado,
.app-table tfoot .socio-aportado,
#pieTotalFilas {
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

/* Fila de un ex-socio en la rejilla del año en curso: sigue visible por los meses
   que ya aportó, atenuada para no confundirla con la lista de socios activos.
   pointer-events en la celda de mes (en vez de tocar el hover) evita anular la
   tinta de :has(input:checked), que es justo lo que hay que seguir viendo. */
.app-table tbody tr.socio-ex td { color: var(--text-muted); }
.app-table tbody tr.socio-ex .socio-mes { pointer-events: none; }

/* Lista de candidatos del diálogo «añadir socios»: scrollea sola para que el pie
   del diálogo (con el botón de confirmar) siga siempre visible. */
.socios-candidatos {
    max-block-size: 45vh;
    overflow-y: auto;
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
}

/* Cada candidato es un <label>: toda la fila selecciona, no solo el checkbox. */
.socios-candidato {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    padding: var(--sp-2) var(--sp-3);
    margin: 0;
    cursor: pointer;
    border-block-end: 1px solid var(--border);
}

.socios-candidato:last-child { border-block-end: none; }

/* El display:flex de arriba gana al [hidden] de la hoja del navegador, así que el
   filtro del buscador no ocultaría nada sin esta regla. */
.socios-candidato[hidden] { display: none; }

.socios-candidato:hover {
    background: color-mix(in oklab, var(--color-action-fg) 8%, transparent);
}

.socios-candidato:has(input:checked) {
    background: color-mix(in oklab, var(--color-action-fg) 16%, transparent);
}

.socios-candidato:has(input:focus-visible) {
    outline: 2px solid var(--ring-focus);
    outline-offset: -2px;
}

.socios-candidato input[type="checkbox"] {
    inline-size: 1.15rem;
    block-size: 1.15rem;
    margin: 0;
    flex-shrink: 0;
    cursor: pointer;
}

/* Nombre arriba, correo debajo en tono secundario. */
.socios-candidato-texto {
    display: flex;
    flex-direction: column;
    min-inline-size: 0;
}

.socios-candidato-texto > * {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Footer de totales (libroDiario, diezmos, envios, libros nacionales).
   Diseñado para colocarse FUERA del .app-table-wrap, como hermano dentro del
   flex-column de .app-page. Así queda anclado al fondo aunque haya pocas filas. */
/* Rejilla en vez de flex con space-around: cada cifra reserva un ancho minimo
   propio y el reparto lo hace el navegador, sin media queries. Con space-around
   los stats se apretaban entre si en cuanto habia cuatro o cinco. */
.app-table-foot {
    flex-shrink: 0;
    background: var(--surface-soft);
    border: 1px solid var(--border);
    border-radius: var(--radius-xl);
    padding: var(--sp-4) var(--sp-5);
    display: grid;
    /* 9rem: lo justo para que las siete cifras del libro diario quepan en una
       sola fila a 1400px sin apretarse. Con mas, se partia en dos y el pie le
       robaba a la tabla el alto que acababa de ganar. */
    grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
    align-items: center;
    gap: var(--sp-3) var(--sp-4);
    box-shadow: var(--shadow-sm);
}

/* Botones intercalados entre los stats: no deben estirarse a lo ancho de su
   celda de la rejilla. */
.app-table-foot > :not(.app-table-foot-stat) {
    justify-self: center;
}

.app-table-foot-stat {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--sp-1);
    text-align: center;
    min-width: 0;
}

.app-table-foot-stat-label {
    font-size: var(--fs-xs);
    color: var(--gray-600);
    text-transform: uppercase;
    letter-spacing: 0.3px;
    white-space: nowrap;
    width: 100%;
    text-align: center;
}

.app-table-foot-stat-value {
    font-size: var(--fs-lg);
    font-weight: var(--fw-semibold);
    color: var(--gray-800);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--sp-1);
}

/* Variantes de color para el value del stat (semántico). */
.app-table-foot-stat--success .app-table-foot-stat-value { color: var(--success); }
.app-table-foot-stat--info    .app-table-foot-stat-value { color: var(--info); }
.app-table-foot-stat--warning .app-table-foot-stat-value { color: var(--warning); }
.app-table-foot-stat--purple  .app-table-foot-stat-value { color: var(--purple); }

/* En movil: columnas mas estrechas para que quepan dos, y menos margen */
@media (max-width: 575.98px) {
    .app-table-foot {
        grid-template-columns: repeat(auto-fit, minmax(7.5rem, 1fr));
        gap: var(--sp-3);
        padding: var(--sp-3);
    }
}

/* Suma de lo filtrado (fragments/totalFiltrado.html + libroBase.js): franja entre la
   tabla y el pie, solo con un filtro puesto. Borde discontinuo para que se lea como
   algo provisional, ligado al filtro, y no como un saldo más del pie. Va ANTES de
   .app-tipo--* y .app-saldo--*: con la misma especificidad gana la última regla, y así
   las cifras de entradas, salidas y neto pueden llevar su color. */
.app-total-filtrado {
    flex-shrink: 0;
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--sp-1) var(--sp-5);
    padding: var(--sp-2) var(--sp-4);
    background: var(--surface-card);
    border: 1px dashed var(--border);
    border-radius: var(--radius-lg);
    font-size: var(--fs-sm);
    color: var(--text-muted);
}

/* El display de arriba le ganaría al [hidden] del navegador */
.app-total-filtrado[hidden] { display: none; }

.app-total-filtrado-titulo {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-1);
    font-weight: var(--fw-semibold);
    color: var(--color-action-fg);
}

.app-total-filtrado-cifra {
    font-weight: var(--fw-semibold);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
    color: var(--text-strong);
}

.app-total-filtrado-nota {
    margin-inline-start: auto;
    font-size: var(--fs-xs);
}

/* Tipo de transacción (libro diario y similares): color según ENTRADA/SALIDA/TRANSFERENCIA. */
.app-tipo--entrada       { color: var(--success); }
.app-tipo--salida        { color: var(--danger); }
.app-tipo--transferencia { color: var(--purple); }
/* Acumulado de los libros: el color lo pone el signo del saldo, no el tipo de fila */
.app-saldo--positivo     { color: var(--success); }
.app-saldo--negativo     { color: var(--danger); }

/* Celda "Tipo" del libro diario: icono direccional + etiqueta, alineados.
   El icono hereda el color de .app-tipo--* del span contenedor. */
.app-tipo-cell {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-1);
}

/* Prefijo de divisa ("zł") atenuado para que destaque la cifra. */
.app-monto-cur {
    color: var(--text-muted);
    font-weight: var(--fw-regular);
    font-size: var(--fs-xs);
}

/* ─────────────────────────────────────────────────────────────────────────
   Control segmentado: radios estilados como pastillas conectadas, para 1–5
   opciones exclusivas (matriz de la guía forms). Sustituye a selects pequeños.
   Estructura: <div class="app-segmented" role="radiogroup">
                 <label><input class="visually-hidden"><span>…</span></label> …
               </div>
   El radio va visualmente oculto (sigue siendo accesible y enfocable); el <span>
   hermano es la pastilla visible (selector input:checked + span, sin :has()).
   ───────────────────────────────────────────────────────────────────────── */
.app-segmented {
    display: inline-flex;
    align-items: stretch;
    flex: none;   /* dentro de un action-bar flex: no encoger (evita que overflow recorte) */
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    overflow: hidden;
    background: var(--surface);
}
.app-segmented label {
    margin: 0;
    display: inline-flex;
    cursor: pointer;
}
.app-segmented label + label { border-inline-start: 1px solid var(--border); }
/* Solo el span-pastilla (hijo directo del label), NO el icono interior, para que
   el icono herede el color del pill (gris si inactivo, blanco si activo). */
.app-segmented label > span {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-1);
    padding: var(--sp-1) var(--sp-3);
    font-size: var(--fs-sm);
    color: var(--text-muted);
    white-space: nowrap;
    transition: background-color 0.15s ease, color 0.15s ease;
}
.app-segmented input:checked + span {
    background: var(--color-action);
    color: var(--text-on-action);
    font-weight: var(--fw-semibold);
}
.app-segmented input:focus-visible + span {
    outline: 2px solid var(--ring-focus);
    outline-offset: -2px;
}
.app-segmented input:disabled + span { opacity: 0.5; cursor: not-allowed; }

/* Variante Tipo: color semántico en el segmento activo (entrada verde / salida roja). */
.app-segmented--tipo input[value="ENTRADA"]:checked + span { background: var(--success); }
.app-segmented--tipo input[value="SALIDA"]:checked + span  { background: var(--danger); }

/* Tooltip estilado (reemplaza al title nativo donde se usa data-tip). Es un
   popover en el top-layer posicionado por JS (inicializarTooltips en main.js),
   así que no lo recorta el overflow de la tabla y se ve igual en todos los temas.
   Texto a --fs-base para que sea legible (el nativo es diminuto). */
.app-tooltip {
    position: fixed;
    margin: 0;
    inset: auto;
    max-inline-size: min(320px, 90vw);
    padding: var(--sp-2) var(--sp-3);
    background: var(--surface-card);
    color: var(--text-strong);
    font-size: var(--fs-base);
    line-height: var(--lh-base);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-floating);
    text-wrap: pretty;
}

/* ═════════════════════════════════════════════════════════════════════════
   8.  FILTROS (barra superior + dropdown filtrable)
   ═════════════════════════════════════════════════════════════════════════ */
.app-filters {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-2);
    align-items: center;
    margin-bottom: var(--sp-3);
}

.app-filters input[type="text"],
.app-filters input[type="search"],
.app-filters select {
    font-size: var(--fs-base);
    padding: var(--sp-1) var(--sp-2);
    border-radius: var(--radius-md);
    border: 1px solid var(--gray-300);
    background: var(--surface);
    height: 32px;
}

.app-filters input[type="text"],
.app-filters input[type="search"] {
    min-width: 180px;
    flex: 1;
    /* Con tope: al llevarse los filtros a la cabecera de la tabla, el buscador se
       quedaba solo en la barra y se estiraba hasta ocupar toda la pantalla, muy
       por encima de lo que se teclea en él. */
    max-inline-size: 28rem;
}

@media (max-width: 767.98px) {
    .app-filters {
        gap: var(--sp-1);
    }
    .app-filters input[type="text"],
    .app-filters input[type="search"] {
        flex-basis: 100%;
        min-width: 0;
    }
    .app-filters select {
        flex: 1 1 calc(50% - var(--sp-1));
        min-width: 0;
    }
    .app-filters .app-btn {
        flex: 0 0 auto;
    }
}

/* Dropdown de filtro multi-select (libroDiario)
   Aplicado SOBRE .dropdown-menu de Bootstrap, por eso pisa su padding base. */
.app-filter-dropdown {
    min-width: 220px;
    max-height: 400px;
    overflow-y: auto;
    padding: 0;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-lg);
    /* El panel de un filtro de columna vive DENTRO de su <th>, así que heredaba
       su tipografía de encabezado: los nombres de catálogo salían en mayúsculas,
       apretados y en gris claro. El top-layer no aísla la herencia, solo el
       apilado, de modo que hay que devolver estos valores a mano. */
    text-transform: none;
    letter-spacing: normal;
    font-weight: var(--fw-regular);
    color: var(--text-default);
    white-space: normal;
}

/* Migrado desde dropdown Bootstrap a popover nativo: vive en el top-layer con
   light-dismiss y se mantiene abierto al clicar dentro. La posición bajo el botón
   ancla la fija posicionarFiltroPopover() en main.js (top/left por JS). */
.app-filter-dropdown[popover] {
    margin: 0;
    inset: auto;
}

.app-filter-header {
    background: var(--surface-soft);
    padding: var(--sp-2) var(--sp-3);
    font-weight: var(--fw-semibold);
    font-size: var(--fs-sm);
    border-bottom: 1px solid var(--border);
    position: sticky;   /* fija arriba mientras scrollea la lista de opciones */
    top: 0;
    z-index: 1;
}

.app-filter-item {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    padding: var(--sp-1) var(--sp-3);
    font-size: var(--fs-sm);
    cursor: pointer;
    transition: background var(--tr-fast);
}

.app-filter-item:hover { background: var(--surface-soft); }
.app-filter-item .form-check-label { cursor: pointer; margin: 0; flex: 1; user-select: none; }

/* Icono de una opción de filtro: repite el símbolo del badge de esa celda para
   que la columna y su panel se lean como lo mismo. Hereda el color salvo que la
   opción declare el de su estado. */
.app-filter-icono {
    font-size: 1.05em;
    color: var(--text-muted);
    flex-shrink: 0;
}

.app-filter-icono--ok        { color: var(--success); }
.app-filter-icono--warn      { color: var(--warning); }
.app-filter-icono--danger    { color: var(--danger); }
.app-filter-icono--none      { color: var(--text-subtle); }
.app-filter-icono--mis-activo { color: var(--purple-soft-text); }

/* Mismo aspecto que el footer de un modal (.app-modal-foot): barra con borde
   superior, botones a su ancho natural alineados a la derecha. Fija abajo
   (sticky) para que la lista de opciones scrollee por detrás sin moverla. */
.app-filter-actions {
    display: flex;
    justify-content: flex-end;
    gap: var(--sp-2);
    padding: var(--sp-3) var(--sp-4);
    border-top: 1px solid var(--border);
    position: sticky;
    bottom: 0;
    background: var(--surface);   /* opaco: la lista no se ve por detrás */
    z-index: 1;
}

/* Panel con TODOS los grupos de filtro seguidos (el que abre el botón «Filtros»
   cuando la columna de alguno está oculta). Más ancho y más alto que un panel de
   columna porque lleva varias secciones, cada una con su propia cabecera sticky. */
.app-filter-dropdown--ancho {
    min-width: 260px;
    max-width: min(340px, calc(100vw - 2 * var(--sp-3)));
    max-height: min(70vh, 520px);
}

/* Cuántos grupos tienen filtro puesto, dentro del botón que abre ese panel. */
.app-filtros-cuenta {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-inline-size: 1.25em;
    padding: 0 0.35em;
    margin-inline-start: var(--sp-1);
    border-radius: var(--radius-pill);
    background: var(--color-action);
    color: #fff;
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
}

/* Disparador de filtros con algo puesto: se distingue sin depender solo del
   número, que es pequeño. */
.app-btn--activo {
    border-color: var(--color-action-fg);
    color: var(--color-action-fg);
}

/* ═════════════════════════════════════════════════════════════════════════
   9.  BOTONES (.app-btn--*)
   ═════════════════════════════════════════════════════════════════════════ */
.app-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--sp-1);
    border: 1px solid transparent;
    border-radius: var(--radius-md);
    padding: 0.4rem var(--sp-3);
    font-size: var(--fs-base);
    font-weight: var(--fw-semibold);
    line-height: 1.2;
    min-height: 32px;
    cursor: pointer;
    transition: background var(--tr-fast), color var(--tr-fast), box-shadow var(--tr-fast);
    text-decoration: none;
    white-space: nowrap;
}

.app-btn:disabled,
.app-btn[disabled] { opacity: 0.55; cursor: not-allowed; }

/* Cada variante declara su color de primer plano vía la custom property
   --app-btn-fg. El botón, el icono <i>, .bi y el pseudo-elemento ::before
   leen siempre la misma propiedad — así no hay batalla de cascada con
   `inherit` ni dependemos de Bootstrap Icons heredando bien. */
.app-btn { color: var(--app-btn-fg) !important; }
.app-btn > i,
.app-btn > .app-icon,
.app-btn .bi,
.app-btn .bi::before {
    color: var(--app-btn-fg) !important;
}

.app-btn--primary {
    --app-btn-fg: var(--text-on-action);
    background: var(--color-action);
}
.app-btn--primary:hover:not(:disabled) {
    --app-btn-fg: var(--text-on-action);
    background: var(--color-action-strong);
    box-shadow: 0 4px 12px rgba(0, 51, 141, 0.3);
}

.app-btn--accent {
    --app-btn-fg: var(--text-on-accent);
    background: var(--color-accent);
}
.app-btn--accent:hover:not(:disabled) {
    --app-btn-fg: var(--color-action-strong);
    background: var(--color-accent-strong);
}

.app-btn--ghost {
    --app-btn-fg: var(--color-action-fg);
    background: transparent;
    border-color: var(--color-action-fg);
}
.app-btn--ghost:hover:not(:disabled) {
    --app-btn-fg: var(--text-on-action);
    background: var(--color-action);
}

/* Contorneado: acción destructiva que NO debe gritar (cerrar sesión en el panel
   de cuenta). Se rellena de rojo al pasar por encima. */
.app-btn--danger-ghost {
    --app-btn-fg: var(--danger);
    background: transparent;
    border-color: var(--danger);
}
.app-btn--danger-ghost:hover:not(:disabled) {
    --app-btn-fg: #FFF;
    background: var(--danger);
}

.app-btn--danger {
    --app-btn-fg: #FFF;
    background: var(--danger);
}
.app-btn--danger:hover:not(:disabled) {
    --app-btn-fg: #FFF;
    background: #B92532;
}

.app-btn--sm {
    padding: 0.25rem var(--sp-2);
    font-size: var(--fs-sm);
    min-height: 28px;
}
.app-btn--lg {
    padding: var(--sp-2) var(--sp-4);
    font-size: var(--fs-md);
    min-height: 40px;
}

/* Botón con SOLO icono: cuadrado fijo, ícono más legible.
   Width/height explícitos (sin aspect-ratio) para predicibilidad en flex containers.
   El color del icono se hereda por la regla genérica .app-btn .bi de arriba. */
.app-btn--icon {
    padding: 0;
    width: 32px;
    height: 32px;
    min-width: 32px;
    flex-shrink: 0;
}
.app-btn--icon > :is(i, .app-icon) { font-size: 1.1em; color: var(--app-btn-fg) !important; }
.app-btn--icon.app-btn--sm { width: 28px; height: 28px; min-width: 28px; }
.app-btn--icon.app-btn--lg { width: 40px; height: 40px; min-width: 40px; }

/* Botón colapsable: oculto por defecto, visible cuando se añade .show.
   !important necesario para vencer min-width de .app-btn--icon y .app-action-bar > .app-btn--icon
   (CSS spec: min-width siempre prevalece sobre max-width, hay que neutralizarlo). */
.app-collapsible {
    overflow: hidden;
    max-width: 0 !important;
    min-width: 0 !important;
    width: 0 !important;
    opacity: 0;
    padding: 0 !important;
    margin: 0;
    border-width: 0;
    transition: max-width var(--tr-base), min-width var(--tr-base), width var(--tr-base),
                opacity var(--tr-base), padding var(--tr-base), margin var(--tr-base);
}

.app-collapsible.show {
    max-width: 200px !important;
    min-width: auto !important;
    width: auto !important;
    opacity: 1;
    padding: var(--sp-1) var(--sp-3) !important;
    border-width: 1px;
}

/* Si el collapsible es a la vez icon-only, restauramos la forma cuadrada al expandir */
.app-collapsible.app-btn--icon.show {
    max-width: 32px !important;
    min-width: 32px !important;
    width: 32px !important;
    padding: 0 !important;
}

/* Botón de acción icono-only en filas de tabla */
.app-btn-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    padding: 0;
    border-radius: var(--radius-sm);
    border: 1px solid var(--gray-300);
    background: var(--surface);
    color: var(--gray-700);
    cursor: pointer;
    transition: background var(--tr-fast), color var(--tr-fast), border-color var(--tr-fast);
}
.app-btn-icon:hover:not(:disabled) {
    border-color: var(--color-action-fg);
    color: var(--color-action-fg);
}
.app-btn-icon--info:hover:not(:disabled)    { color: var(--info);    border-color: var(--info); }
.app-btn-icon--warn:hover:not(:disabled)    { color: var(--warning); border-color: var(--warning); }
.app-btn-icon--danger:hover:not(:disabled)  { color: var(--danger);  border-color: var(--danger); }
.app-btn-icon--success:hover:not(:disabled) { color: var(--success); border-color: var(--success); }
.app-btn-icon:disabled { opacity: 0.45; cursor: not-allowed; }

.app-actions {
    display: flex;
    gap: var(--sp-1);
    flex-wrap: nowrap;
}

/* Barra contextual de acciones masivas (selección de filas → ejecutar acción).
   También usado para forms inline de "nueva entrada" — altura consistente
   garantizada por min-height en hijos directos. */
.app-action-bar {
    flex-shrink: 0;
    background: var(--surface-card);
    border: 1px solid var(--color-action-fg);
    border-radius: var(--radius-xl);
    box-shadow: var(--shadow-card);
    padding: var(--sp-2) var(--sp-3);
    min-height: 56px;
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    transition: box-shadow var(--tr-base);
}

.app-action-bar:hover { box-shadow: var(--shadow-elevated); }

/* Inputs/selects/buttons del action-bar (form inline) homogéneos con form-control-sm */
.app-action-bar > .form-control,
.app-action-bar > .form-select,
.app-action-bar > .form-control-sm,
.app-action-bar > .form-select-sm {
    min-height: 32px;
    font-size: var(--fs-base);
}

.app-action-bar > .app-btn { min-height: 32px; }
.app-action-bar > .app-btn--sm { min-height: 32px; }
.app-action-bar > .app-btn--icon { width: 32px; height: 32px; min-width: 32px; }

.app-action-bar-summary {
    font-weight: var(--fw-bold);
    color: var(--color-action-fg);
    white-space: nowrap;
    display: flex;
    align-items: center;
    gap: var(--sp-1);
}

/* ═════════════════════════════════════════════════════════════════════════
   10. BADGES
   ═════════════════════════════════════════════════════════════════════════ */
.app-badge {
    display: inline-flex;
    align-items: center;
    gap: 0.2rem;
    font-size: var(--fs-xs);
    padding: 0.18rem 0.45rem;
    border-radius: var(--radius-md);
    white-space: nowrap;
    font-weight: var(--fw-semibold);
}

.app-badge--success { background: var(--success-soft); color: var(--success-soft-text); }
.app-badge--warning { background: var(--warning-soft); color: var(--warning-soft-text); }
.app-badge--danger  { background: var(--danger-soft);  color: var(--danger-soft-text); }
.app-badge--info    { background: var(--info-soft);    color: var(--info-soft-text); }
.app-badge--neutral { background: var(--neutral-soft); color: var(--neutral-soft-text); }

/* Estados de membresía */
.app-badge--estado-activo      { background: var(--success-soft); color: var(--success-soft-text); }
.app-badge--estado-inactivo    { background: var(--danger-soft);  color: var(--danger-soft-text); }
.app-badge--estado-trasladado  { background: var(--info-soft);    color: var(--info-soft-text); }
.app-badge--estado-fallecido   { background: var(--neutral-soft); color: var(--neutral-soft-text); }
.app-badge--estado-suspendido  { background: var(--warning-soft); color: var(--warning-soft-text); }

/* Estado de transacción anulada (libro diario): fondo rojo translúcido, ayuda visual de tooltip */
.app-badge--anulada {
    background: color-mix(in srgb, var(--danger) 40%, transparent);
    color: #FFF;
    cursor: help;
}

/* Fuentes de fondos en envíos: cada una con un color soft distinto */
.app-badge--fuente-LOCAL         { background: var(--success-soft); color: var(--success-soft-text); }
.app-badge--fuente-BANCARIA      { background: var(--info-soft);    color: var(--info-soft-text); }
.app-badge--fuente-DIEZMOS       { background: var(--warning-soft); color: var(--warning-soft-text); }
.app-badge--fuente-DIEZMO_DIEZMO { background: light-dark(#FFE5B4, #3A2A12); color: light-dark(#6B3E00, #F0C089); }

/* Seguimiento pastoral */
.app-badge--seg-ok     { background: var(--success-soft); color: var(--success-soft-text); }
.app-badge--seg-warn   { background: var(--warning-soft); color: var(--warning-soft-text); }
.app-badge--seg-danger { background: var(--danger-soft);  color: var(--danger-soft-text); }
.app-badge--seg-none   { background: var(--neutral-soft); color: var(--gray-600); }

/* ── Notas de seguimiento (ficha del miembro) ────────────────────────────────
   El misionero y el pastor conviven en la misma lista, así que cada nota se
   distingue por el rol del autor: banda de color a la izquierda + icono +
   etiqueta de texto (el color nunca es el único indicador, WCAG 1.4.1).
   El morado del misionero es el mismo que su badge en el listado nacional. */
.app-nota-texto {
    font-size: var(--fs-base);
    color: var(--text-strong);
    margin: 0;
    white-space: pre-wrap;
    word-break: break-word;
}

.app-nota-meta {
    font-size: var(--fs-xs);
    color: var(--text-subtle);
    margin: var(--sp-1) 0 0;
    display: flex;
    align-items: center;
    gap: 0.15rem;
}

.app-nota-rol {
    margin-inline-start: 0.3rem;
    padding: 0.05rem 0.35rem;
    border-radius: var(--radius-sm);
    font-weight: var(--fw-semibold);
    background: var(--neutral-soft);
    color: var(--neutral-soft-text);
}

.app-nota--misionero {
    border-inline-start: 3px solid var(--purple-soft-text);
}

.app-nota--misionero .app-nota-rol {
    background: var(--purple-soft);
    color: var(--purple-soft-text);
}

.app-nota--misionero .app-nota-meta .app-icon { color: var(--purple-soft-text); }

.app-nota--pastor {
    border-inline-start: 3px solid var(--color-action-fg);
}

.app-nota--pastor .app-nota-rol {
    background: var(--color-action-soft);
    color: var(--color-action-fg);
}

.app-nota--pastor .app-nota-meta .app-icon { color: var(--color-action-fg); }

/* Seguimiento a cargo del misionero (listado nacional).
   Morado: no colisiona con los colores que Estado y "Sin seg." ya usan en la
   misma tabla. El par -soft/-soft-text ya está resuelto con light-dark().
   El estado se acompaña SIEMPRE de icono + texto: el color nunca es el único
   indicador (WCAG 1.4.1). */
.app-badge--mis-activo    { background: var(--purple-soft);  color: var(--purple-soft-text); }
.app-badge--mis-historico { background: var(--neutral-soft);  color: var(--neutral-soft-text); }
.app-badge--mis-none      { background: transparent; color: var(--text-subtle); border: 1px dashed var(--border-strong); }

/* Badge que además es botón: el toggle con el que el misionero marca/desmarca
   de quién lleva el seguimiento. El estado lo comunica aria-pressed. */
.app-badge--btn {
    border: 0;
    cursor: pointer;
    font-family: inherit;
}

.app-badge--btn.app-badge--mis-none { border: 1px dashed var(--border-strong); }

/* Ring con currentColor: se adapta solo a claro/oscuro y a cada variante. */
.app-badge--btn:hover:not(:disabled) {
    box-shadow: 0 0 0 2px color-mix(in srgb, currentColor 35%, transparent);
}

.app-badge--btn:disabled {
    opacity: 0.6;
    cursor: progress;
}

/* Flag (espíritu santo, servidor activo, cabeza hogar, etc.) */
.app-badge--flag {
    font-size: var(--fs-xs);
    padding: 0.12rem 0.38rem;
}

/* Punto rojo de notificación no leída (panel del navbar). Autónomo: ya no
   depende de .badge/.bg-danger/.rounded-pill de Bootstrap. */
.app-notif-dot {
    inline-size: 8px;
    block-size: 8px;
    flex-shrink: 0;
    padding: 0;
    border-radius: var(--radius-pill);
    background: var(--danger);
}

/* ═════════════════════════════════════════════════════════════════════════
   11. MODAL / DIALOG (cabecera azul corporativa)

   Dos variantes conviven durante la migración:
   - Legacy Bootstrap `.modal` (en vistas aún sin migrar): usa .app-modal-head/-foot.
   - Nativo `<dialog class="app-dialog">`: reusa .app-modal-head/-foot y añade
     contenedor + body + título propios. Ver docs/ui-modernizacion-2026-plan.md.
   ═════════════════════════════════════════════════════════════════════════ */
.app-modal-head {
    background: var(--color-action);
    color: var(--text-on-action);
    padding: var(--sp-3) var(--sp-4);
    display: flex;
    justify-content: space-between;
    align-items: center;
    border-bottom: none;
    flex-shrink: 0;   /* como hijo flex de .app-dialog, no se comprime */
}

.app-modal-head .modal-title,
.app-dialog-title {
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
    color: var(--text-on-action);
    margin: 0;
}

/* Variante peligrosa (anulación, eliminación destructiva) */
.app-modal-head--danger { background: var(--danger); }

/* Variante con gradiente corporativo (flujos formales: nuevo envío, detalle) */
.app-modal-head--gradient { background: var(--grad-primary); }

.app-modal-foot {
    display: flex;
    justify-content: flex-end;
    gap: var(--sp-2);
    padding: var(--sp-3) var(--sp-4);
    border-top: 1px solid var(--border);
    flex-shrink: 0;   /* como hijo flex de .app-dialog, no se comprime */
}

/* ── <dialog> nativo: contenedor en el top-layer (focus-trap, Esc y
   ::backdrop nativos). Reemplaza a Bootstrap .modal/.modal-dialog/.modal-content. */
.app-dialog {
    padding: 0;
    border: none;
    border-radius: var(--radius-lg);
    background: var(--surface-card);
    color: var(--text-default);
    box-shadow: var(--shadow-floating);
    inline-size: calc(100dvi - 2 * var(--sp-4));
    max-inline-size: 500px;
    max-block-size: calc(100dvb - 2 * var(--sp-5));
    overflow: hidden;            /* scrollea el body, no el dialog completo */
    flex-direction: column;      /* persiste durante la salida (display lo mantiene
                                    allow-discrete; si flex-direction viviera solo en
                                    [open], revertiría a row al cerrar y el footer
                                    saltaría a la derecha unos ms). */

    /* Animación de entrada/salida en el top-layer (animate-to-from-top-layer).
       overlay solo en Chrome/Edge; degrada a aparición instantánea en el resto. */
    opacity: 0;
    transform: translateY(8px) scale(0.98);
    transition-property: opacity, transform, display, overlay;
    transition-duration: 0.2s;
    transition-timing-function: ease-out;
    transition-behavior: allow-discrete;
}

.app-dialog[open] {
    display: flex;
    opacity: 1;
    transform: none;

    @starting-style {
        opacity: 0;
        transform: translateY(8px) scale(0.98);
    }
}

.app-dialog--lg { max-inline-size: 800px; }
.app-dialog--sm { max-inline-size: 380px; }
/* Modal ancho y alto para formularios extensos multi-sección (p.ej. nuevo envío). */
.app-dialog--xl {
    max-inline-size: min(1100px, 95vw);
    block-size: 90dvb;
}

.app-dialog::backdrop {
    background: var(--surface-backdrop);
    backdrop-filter: blur(2px);
}

/* Toasts promovidos a popover (main.js) para verse sobre el ::backdrop de un modal:
   el UA centra el popover con `inset: 0; margin: auto` — lo neutralizamos para que la
   posición la den las utilidades Bootstrap (bottom-0/end-0/top-0/start-50). El fondo,
   borde y padding los aporta .alert/.toast (gana al estilo de UA). */
.alert[popover],
.toast[popover] {
    inset: auto;
    width: auto;
}

.app-dialog-body {
    padding: var(--sp-4);
    overflow-y: auto;            /* cuerpo scrollable; cabecera/pie fijos */
    flex: 1 1 auto;
}

.app-dialog-close {
    background: transparent;
    border: none;
    color: var(--text-on-action);
    font-size: var(--fs-lg);
    line-height: 1;
    padding: var(--sp-1);
    border-radius: var(--radius-sm);
    cursor: pointer;
    display: inline-flex;
}
.app-dialog-close:hover { opacity: 0.8; }

@media (prefers-reduced-motion: reduce) {
    .app-dialog {
        transform: none;
        transition-duration: 0.01ms;
    }
    .app-dialog[open] {
        @starting-style { transform: none; }
    }
}

/* ── Diálogo de confirmación / aviso (generado por confirmar()/avisar() en
   main.js; reemplaza a SweetAlert2). Icono coloreado por intención + texto centrado. */
.app-dialog--confirm { text-align: center; }

.app-dialog-confirm-body {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--sp-2);
    padding-block: var(--sp-5) var(--sp-4);
}

.app-dialog-confirm-icon {
    font-size: 2.75rem;
    line-height: 1;
    margin-bottom: var(--sp-1);
}
.app-dialog-confirm-icon--warning  { color: var(--warning); }
.app-dialog-confirm-icon--question { color: var(--color-action-fg); }
.app-dialog-confirm-icon--success  { color: var(--success); }
.app-dialog-confirm-icon--error    { color: var(--danger); }
.app-dialog-confirm-icon--info     { color: var(--info); }

.app-dialog-confirm-title {
    font-size: var(--fs-lg);
    font-weight: var(--fw-semibold);
    color: var(--text-strong);
    margin: 0;
}

.app-dialog-confirm-msg {
    color: var(--text-muted);
    margin: 0;
}

.app-dialog--confirm .app-modal-foot { justify-content: center; }

/* ═════════════════════════════════════════════════════════════════════════
   ICONOS — Material Symbols (variable font auto-hospedada, subset ~98 glifos,
   /fonts/material-symbols.woff2). Reemplaza a Bootstrap Icons.
   Markup: <span class="app-icon" aria-hidden="true">home</span>
   (el nombre del icono es el texto → se renderiza como glifo vía ligadura).
   Mapeo bi→Material en docs/iconos-material-symbols-map.md.
   ═════════════════════════════════════════════════════════════════════════ */
@font-face {
    font-family: 'Material Symbols Outlined';
    font-style: normal;
    font-weight: 100 700;
    font-display: block;   /* evita el flash del texto-ligadura: el glifo aparece al cargar */
    src: url('/fonts/material-symbols.woff2') format('woff2');
}

.app-icon {
    font-family: 'Material Symbols Outlined';
    font-weight: normal;
    font-style: normal;
    font-size: 1.2em;            /* relativo al texto contiguo */
    line-height: 1;
    letter-spacing: normal;
    text-transform: none;
    display: inline-block;
    white-space: nowrap;
    word-wrap: normal;
    direction: ltr;
    vertical-align: middle;
    user-select: none;
    -webkit-font-smoothing: antialiased;
    font-feature-settings: 'liga';   /* ligaduras: el nombre del icono → glifo */
    font-variation-settings:
        'FILL' var(--icon-fill, 0),
        'wght' var(--icon-wght, 400),
        'GRAD' var(--icon-grad, 0),
        'opsz' var(--icon-opsz, 24);
}

/* Variante rellena (FILL=1) para iconos de estado activo (p.ej. filtro aplicado). */
.app-icon--fill { --icon-fill: 1; }

/* Tamaños relativos al contexto. */
.app-icon--sm { font-size: 1em; }
.app-icon--lg { font-size: 1.5em; }
.app-icon--xl { font-size: 2.75em; }   /* estados vacíos, hero */

/* ═════════════════════════════════════════════════════════════════════════
   12. ESTADO VACÍO / LOADING
   ═════════════════════════════════════════════════════════════════════════ */
.app-empty {
    text-align: center;
    color: var(--gray-500);
    font-size: var(--fs-base);
    padding: var(--sp-7) 0;
}

.app-empty i,
.app-empty .app-icon {
    margin-right: var(--sp-1);
    font-size: var(--fs-4xl);
    color: var(--gray-400);
}

.app-spinner-center {
    display: flex;
    align-items: center;
    justify-content: center;
    padding: var(--sp-6) 0;
}

/* Página de error (error.html): ocupa el alto de la vista y el icono manda */
.app-error-page  { min-block-size: 60vh; }
.app-error-icono { font-size: calc(var(--fs-4xl) * 2.25); }

/* ═════════════════════════════════════════════════════════════════════════
   13. ALERTA INLINE (seguimiento OK/WARN/DANGER en ficha)
   ═════════════════════════════════════════════════════════════════════════ */
.app-alert {
    padding: var(--sp-2) var(--sp-3);
    border-radius: var(--radius-lg);
    font-size: var(--fs-sm);
    font-weight: var(--fw-medium);
    margin-bottom: var(--sp-2);
    display: flex;
    align-items: center;
    gap: var(--sp-1);
}

.app-alert--success { background: var(--success-soft); color: var(--success-soft-text); }
.app-alert--warning { background: var(--warning-soft); color: var(--warning-soft-text); }
.app-alert--danger  { background: var(--danger-soft);  color: var(--danger-soft-text); animation: app-pulse 2s infinite; }
.app-alert--info    { background: var(--info-soft);    color: var(--info-soft-text); }

@keyframes app-pulse {
    0%, 100% { opacity: 1; }
    50%      { opacity: 0.7; }
}

/* ═════════════════════════════════════════════════════════════════════════
   14. ACCORDION jerárquico (admin/distritos)
   ═════════════════════════════════════════════════════════════════════════ */
.app-accordion-item {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-xl);
    margin-bottom: var(--sp-2);
    overflow: hidden;
}

.app-accordion-header {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    padding: var(--sp-3) var(--sp-4);
    cursor: pointer;
    transition: background var(--tr-fast);
}

.app-accordion-header:hover { background: var(--surface-soft); }

.app-accordion-chevron {
    transition: transform var(--tr-base);
    color: var(--gray-500);
}

.app-accordion-item[aria-expanded="true"] .app-accordion-chevron,
.app-accordion-item.is-open .app-accordion-chevron { transform: rotate(90deg); }

.app-accordion-body {
    padding: 0 var(--sp-4) var(--sp-3);
    border-top: 1px solid var(--border-soft);
}

/* Bloque de título dentro del header del acordeón (nombre + descripción). */
.app-accordion-title {
    flex: 1;
    min-width: 0;
}
.app-accordion-title-name {
    margin: 0;
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
    color: var(--gray-800);
}
.app-accordion-title-desc {
    margin: 0;
    font-size: var(--fs-sm);
    color: var(--gray-600);
}

/* Lista plana dentro de un acordeón abierto (p.ej. iglesias del distrito). */
.app-accordion-list {
    list-style: none;
    margin: 0;
    padding: 0;
}
.app-accordion-list-item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-2);
    padding: var(--sp-2);
    border-radius: var(--radius-sm);
    font-size: var(--fs-base);
}
.app-accordion-list-name {
    font-weight: var(--fw-medium);
    color: var(--gray-800);
}
.app-accordion-list-meta {
    margin-left: var(--sp-2);
    font-size: var(--fs-xs);
    color: var(--gray-500);
}

/* ═════════════════════════════════════════════════════════════════════════
   15. SEARCH RESULTS (dropdown absolute, admin/pastores)
   ═════════════════════════════════════════════════════════════════════════ */
.app-search-wrap { position: relative; }

.app-search-results {
    display: none;
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    margin-top: var(--sp-1);
    background: var(--surface-card);
    border: 1px solid var(--border-default);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-elevated);
    max-height: 280px;
    overflow-y: auto;
    z-index: var(--z-dropdown);
}

.app-search-results.show { display: block; }

.app-search-item {
    padding: var(--sp-2) var(--sp-3);
    cursor: pointer;
    border-bottom: 1px solid var(--border-soft);
    font-size: var(--fs-sm);
}

.app-search-item:last-child { border-bottom: none; }
.app-search-item:hover      { background: var(--surface-soft); }

/* ═════════════════════════════════════════════════════════════════════════
   14a. CENSO DE MEMBRESÍA (comun/censoUi.js, local y nacional)

   Un solo censo para los dos ámbitos (M4, 2026-11-03). Tarjetas: las .app-stat de
   siempre, todas con número, etiqueta, línea secundaria e icono para que salgan
   del mismo alto. Paneles en una rejilla de tres columnas: las tablas ocupan dos
   (Bautismo y Espíritu Santo, Por distrito e iglesia) y a su lado va un panel de
   barras; debajo, los tres catálogos. Los paneles de catálogo pintan todos sus
   valores aunque estén a 0, así que no cambian de tamaño según los datos.
   El ancho de cada barra va inline porque es un dato calculado, igual que --c.
   ═════════════════════════════════════════════════════════════════════════ */
.app-censo-grid {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: var(--sp-3);
}
.app-censo-panel--ancho { grid-column: span 2; }

/* Un poco más denso en vertical que las tarjetas y barras del resto de la app: con
   la densidad de serie, el censo local se pasaba unos 30 px de una pantalla de altura
   media (838 px de ventana) y salía una barra de desplazamiento que movía casi nada
   (visto por el usuario el 2026-11-03). En móvil ya manda su propio relleno. */
@media (min-width: 576px) {
    .app-censo .app-stat { padding-block: var(--sp-3); }
    .app-censo .app-stats-grid { margin-bottom: var(--sp-3); }
}
.app-censo .app-barra-fila { margin-block-end: var(--sp-1); }
.app-censo .app-barra-fila:last-child { margin-block-end: 0; }

/* Media query: se escribió cuando @container app-page no funcionaba (ver .app-page).
   Para el censo da lo mismo, porque .app-page ocupa todo el ancho. */
@media (max-width: 991.98px) {
    .app-censo-grid { grid-template-columns: minmax(0, 1fr); }
    .app-censo-panel--ancho { grid-column: auto; }
}

/* Fila de barra: etiqueta | pista | cifra. En rejilla y no en flex para que las
   pistas de un mismo panel empiecen y acaben a la vez aunque las etiquetas
   midan distinto («Miembro en proceso de formación ministerial» parte en dos). */
.app-barra-fila {
    display: grid;
    grid-template-columns: minmax(6rem, 42%) minmax(3rem, 1fr) auto;
    align-items: center;
    gap: var(--sp-2);
    margin-block-end: var(--sp-2);
}
.app-barra-fila:last-child { margin-block-end: 0; }

.app-barra-etiqueta {
    font-size: var(--fs-sm);
    color: var(--text-default);
    overflow-wrap: anywhere;
}

.app-barra-pista {
    block-size: 8px;
    border-radius: var(--radius-pill);
    background: var(--surface-soft);
    overflow: hidden;
}

.app-barra-valor {
    block-size: 100%;
    border-radius: var(--radius-pill);
    background: var(--ipue-celeste);
}
/* Primer plano sobre la pista: --color-action-fg y no --ipue-azul, que en oscuro
   no se distingue del fondo. */
.app-barra--accion  { background: var(--color-action-fg); }
.app-barra--celeste { background: var(--ipue-celeste); }
.app-barra--dorado  { background: var(--ipue-dorado); }
.app-barra--exito   { background: var(--success); }
.app-barra--aviso   { background: var(--warning); }
.app-barra--peligro { background: var(--danger); }
.app-barra--neutro  { background: var(--gray-400); }

.app-barra-num {
    font-size: var(--fs-sm);
    min-inline-size: 4.5rem;
    text-align: end;
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
}
.app-barra-num small { color: var(--text-muted); }

/* Tablas del censo (desglose y geografía): cabeceras de fila a la izquierda y
   en peso normal; los subtotales de distrito y el total, resaltados. */
.app-censo-tabla { inline-size: 100%; }
.app-censo-tabla :is(tbody, tfoot) th {
    text-align: start;
    font-weight: var(--fw-medium);
    font-size: var(--fs-sm);
    padding: var(--sp-1) var(--sp-2);
}
.app-censo-tabla tfoot td {
    padding: var(--sp-1) var(--sp-2);
    font-size: var(--fs-sm);
}
.app-censo-tabla tfoot td.num {
    text-align: right;
    font-variant-numeric: tabular-nums;
}
.app-censo-tabla tfoot :is(th, td) { border-block-start: 1px solid var(--border); }
.app-censo-tabla .app-censo-subtotal :is(th, td) {
    font-weight: var(--fw-bold);
    background: var(--surface-soft);
}
.app-censo-tabla .app-censo-iglesia { padding-inline-start: var(--sp-4); }
.app-censo-total small { color: var(--text-muted); font-weight: var(--fw-regular); }

.app-censo-nota {
    font-size: var(--fs-xs);
    color: var(--text-muted);
    margin: var(--sp-2) 0 0;
    text-wrap: pretty;
}

.app-censo-vacio {
    font-size: var(--fs-sm);
    color: var(--text-muted);
    margin: 0;
}

/* Subtítulo de sección con icono (censo). */
.app-subtitulo-icono {
    display: flex;
    align-items: center;
    gap: var(--sp-1);
    flex-wrap: wrap;
}

/* Celda de indicadores de la tabla: separa los badges entre sí.
   Por margen y NO por gap: la celda lleva las utilidades d-*-table-cell de
   Bootstrap, que imponen `display: table-cell !important` y anularían cualquier
   `display: flex` que pusiéramos aquí, dejando el gap sin efecto.
   Los badges son inline-flex, así que el margen sí les aplica. */
.app-flags-celda .app-badge + .app-badge {
    margin-inline-start: var(--sp-1);
}

/* ═════════════════════════════════════════════════════════════════════════
   14b. FICHA DEL MIEMBRO (membresia/ficha.html)

   La cabecera responde «quién es» de un vistazo, en una sola línea: antetítulo,
   nombre, iglesia, distrito, estado y seguimiento, con las acciones a la derecha. La
   pestaña «Datos» lo cuenta entero en tres tarjetas a todo el ancho. Hasta el
   2026-11-02 había además una tarjeta fija a la izquierda que estrechaba todo.
   ═════════════════════════════════════════════════════════════════════════ */
.app-ficha-cabecera {
    align-items: center;
    margin-block-end: var(--sp-2);
}

.app-ficha-titulo {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-2) var(--sp-3);
    min-inline-size: 0;
}

/* Separadores finos entre antetítulo, nombre y contexto, para que la línea se lea
   como tres trozos y no como una sola frase. */
.app-ficha-titulo > .app-page-title,
.app-ficha-titulo > .app-ficha-contexto {
    padding-inline-start: var(--sp-3);
    border-inline-start: 1px solid var(--border);
}

.app-ficha-contexto {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-1) var(--sp-3);
    margin: 0;
    font-size: var(--fs-sm);
    color: var(--text-muted);
}

.app-ficha-dato {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-1);
}

.app-ficha-dato .app-icon { font-size: 1.1em; }

/* Tarjetas de la pestaña «Datos»: <article> + <h2> + <dl> (patrón «card» de
   modern-web-guidance). La rejilla se reparte el ancho; cada tarjeta es su propio
   contenedor para decidir si etiqueta y valor caben en una fila. */
.app-ficha-datos {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 21rem), 1fr));
    gap: var(--sp-4);
}

.app-ficha-tarjeta {
    container-type: inline-size;
    background: var(--surface-card);
    border: 1px solid var(--border);
    border-radius: var(--radius-xl);
    padding: var(--sp-4) var(--sp-5);
    box-shadow: var(--shadow-sm);
}

.app-ficha-tarjeta > h2 {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    margin: 0 0 var(--sp-2);
    padding-block-end: var(--sp-2);
    border-block-end: 1px solid var(--border);
    font-size: var(--fs-base);
    font-weight: var(--fw-bold);
    color: var(--text-strong);
}

.app-ficha-tarjeta > h2 .app-icon { color: var(--color-action-fg); }

/* Lista de descripción: cada <div> es un par <dt>/<dd>. Se pisan los márgenes y el
   negrita que Bootstrap (reboot) pone a dt/dd. */
.app-datos { margin: 0; }

.app-datos > div {
    display: grid;
    grid-template-columns: minmax(7rem, 2fr) 3fr;
    gap: var(--sp-3);
    align-items: baseline;
    padding-block: var(--sp-2);
    border-block-end: 1px solid var(--border-soft);
}

.app-datos > div:last-child { border-block-end: none; }

.app-datos dt {
    font-weight: var(--fw-regular);
    color: var(--text-muted);
}

.app-datos dd {
    margin: 0;
    text-align: end;
    font-weight: var(--fw-medium);
    color: var(--text-strong);
    overflow-wrap: anywhere;
}

/* Tarjeta muy estrecha: la etiqueta encima del valor. En un móvil normal (390px)
   aún caben lado a lado, y apilarlas duplicaba el largo de la ficha. */
@container (max-inline-size: 16rem) {
    .app-datos > div {
        grid-template-columns: 1fr;
        gap: 0;
    }
    .app-datos dd { text-align: start; }
}

/* Acciones sobre el miembro (editar, trasladar, desactivar). Viven en la
   cabecera de la ficha y no en el listado: se opera donde se ven los datos. */
.app-ficha-acciones {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    flex-wrap: wrap;
}

/* ── Fila de tabla que lleva al detalle ──────────────────────────────────────
   El destino real es el <a> de la celda de nombre (teclado, clic central,
   abrir en pestaña nueva); esto solo amplía la zona de clic con el ratón. */
.app-fila-clicable { cursor: pointer; }
.app-fila-clicable:hover { background: var(--surface-soft); }

/* El nombre es un enlace de verdad (teclado, clic central, abrir en pestaña nueva)
   pero ya NO lo parece: sin azul ni subrayado, a petición del usuario el 2026-11-03
   («está feo»), en las cuatro listas de membresía. La señal estática de que la fila
   lleva a la ficha es el chevron del final (.app-fila-ir), que no depende del hover;
   al pasar por encima, la fila entera se resalta. */
.app-fila-enlace {
    font-weight: var(--fw-semibold);
    color: var(--text-strong);
    text-decoration: none;
}

/* Primera línea de la celda de nombre: el nombre (y «Pastor») a la izquierda y las
   marcas de estado en un hueco FIJO, pegado al borde derecho de la columna, para que
   salgan alineadas fila con fila y no bailen según lo largo que sea el nombre
   (pedido por el usuario el 2026-11-03, marca «En disciplina»). */
.app-fila-nombre {
    display: flex;
    align-items: center;
    gap: var(--sp-1) var(--sp-2);
}
.app-fila-nombre .app-fila-enlace { min-inline-size: 0; }
.app-fila-marca {
    margin-inline-start: auto;
    flex-shrink: 0;
}

/* Flecha de "lleva a la ficha". Decorativa: se atenúa y se refuerza con la fila. */
.app-fila-ir {
    inline-size: 1px;              /* que no robe ancho a las columnas con datos */
    text-align: end;
    color: var(--text-muted);
}
.app-fila-clicable:hover .app-fila-ir { color: var(--color-action-fg); }
/* El foco se pinta sobre la fila entera: el anillo alrededor del texto solo
   dentro de una celda se pierde de vista. */
.app-fila-clicable:has(.app-fila-enlace:focus-visible) {
    outline: var(--ring-focus);
    outline-offset: -2px;
}

/* Resumen de la fila para pantallas estrechas (listados de membresía).
   Por debajo de md las columnas de sexo, edad, estado y seguimiento están
   ocultas y la tabla forzaba 650px de ancho mínimo: la fila se quedaba en un
   nombre suelto y el resto se iba fuera de pantalla, detrás de un scroll
   horizontal que no se anunciaba. Aquí esos datos vuelven, bajo el nombre.
   Solo uno de los dos juegos está en el DOM visible a la vez, así que el lector
   de pantalla nunca oye el dato dos veces. */
.app-fila-resumen {
    display: none;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-1) var(--sp-2);
    margin-block-start: var(--sp-1);
    font-weight: var(--fw-regular);
}

.app-fila-resumen-dato {
    font-size: var(--fs-sm);
    color: var(--text-muted);
}

/* El breakpoint es el de Bootstrap (md) a propósito: las columnas se ocultan
   con d-none/d-md-table-cell, que son utilidades de VIEWPORT. Una container
   query aquí se desincronizaría del resto de la tabla. */
@media (max-width: 767.98px) {
    .app-table--miembros { min-width: 0; }
    .app-table--miembros .app-fila-resumen { display: flex; }
}

/* Entradas de historial (congregaciones y pastoral): mismo formato en ambas. */
.app-historial-item {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    margin-block-end: var(--sp-2);
}

.app-historial-texto {
    flex: 1 1 auto;
    min-inline-size: 0;
}

.app-historial-titulo {
    margin: 0;
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
    color: var(--text-strong);
    overflow-wrap: anywhere;
}

.app-historial-meta {
    margin: 0;
    font-size: var(--fs-sm);
    color: var(--text-muted);
    overflow-wrap: anywhere;
}

/* El auto-resize lo hace el JS; aquí solo se desactiva el tirador manual. */
.app-textarea-fija { resize: none; }

/* Pestaña deshabilitada por permisos (ficha: seguimiento). */
.app-tabs .nav-link.disabled {
    opacity: .45;
    pointer-events: none;
}

/* ═════════════════════════════════════════════════════════════════════════
   15a. FIELDSETS DE FORMULARIO LARGO (modal de miembro)

   Agrupan por intención en vez de repartir el formulario en pestañas: dentro de
   un <dialog>, reportValidity() no puede enfocar un campo obligatorio que esté
   oculto en una pestaña inactiva, y la validación falla sin decir nada.
   ═════════════════════════════════════════════════════════════════════════ */
.app-fieldset {
    border: 0;
    padding: 0;
    margin-block-end: var(--sp-5);
}
.app-fieldset:last-of-type { margin-block-end: 0; }

.app-fieldset-titulo {
    float: none;              /* Bootstrap pone width:100% en legend; lo neutralizamos */
    inline-size: auto;
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    text-transform: uppercase;
    letter-spacing: .04em;
    color: var(--text-muted);
    padding-block-end: var(--sp-1);
    margin-block-end: var(--sp-2);
    border-block-end: 1px solid var(--border-soft);
    inline-size: 100%;
}

/* Fila de casillas del bloque de indicadores. */
.app-checks {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-4);
}
.app-checks .form-check-label { font-size: var(--fs-base); }

/* Icono de ayuda junto a una etiqueta: lleva data-tip, así que es interactivo. */
.app-ayuda { cursor: help; }

/* Contador de resultados en la barra de filtros. */
.app-filters-contador {
    font-size: var(--fs-sm);
    color: var(--text-muted);
}

/* ═════════════════════════════════════════════════════════════════════════
   15b. COMBOBOX + BLOQUE DE DIRECCIÓN (fragments/direccion.html)

   A diferencia de .app-search-results (absolute), la lista es un [popover] en el
   top-layer: dentro de .app-dialog-body, que hace overflow-y:auto, un desplegable
   absolute quedaría recortado. La posiciona main.js vía data-anclado-a.
   ═════════════════════════════════════════════════════════════════════════ */
.app-combobox { position: relative; }

.app-combobox-lista[popover] {
    /* El UA centra los popover con inset:0;margin:auto — lo neutralizamos porque
       la posición la calcula posicionarPopoverAnclado(). */
    inset: auto;
    margin: 0;
    padding: 0;
    list-style: none;
    background: var(--surface-card);
    border: 1px solid var(--border-default);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-elevated);
    max-block-size: 280px;
    overflow-y: auto;
    /* El ancho lo fija el JS igualándolo al del campo; esto es solo el suelo. */
    min-inline-size: 14rem;
}

.app-combobox-opcion {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: var(--sp-2) var(--sp-3);
    cursor: pointer;
    border-block-end: 1px solid var(--border-soft);
}
.app-combobox-opcion:last-child { border-block-end: none; }
.app-combobox-opcion:hover,
.app-combobox-opcion.is-activa { background: var(--surface-soft); }

.app-combobox-principal {
    font-size: var(--fs-base);
    color: var(--text-strong);
}
.app-combobox-secundario {
    font-size: var(--fs-sm);
    color: var(--text-muted);
}

.app-combobox-vacio {
    padding: var(--sp-3);
    font-size: var(--fs-sm);
    color: var(--text-muted);
}

/* ── Bloque de dirección ─────────────────────────────────────────────────── */
.app-form-hint {
    font-size: var(--fs-sm);
    color: var(--text-muted);
    margin-block: var(--sp-1) 0;
}

.app-direccion-resumen {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    flex-wrap: wrap;
    padding: var(--sp-3);
    background: var(--surface-soft);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
}

.app-direccion-texto { flex: 1 1 12rem; min-inline-size: 0; }

.app-direccion-linea1 {
    margin: 0;
    font-weight: var(--fw-semibold);
    color: var(--text-strong);
    overflow-wrap: anywhere;
}
.app-direccion-linea2 {
    margin: 0;
    font-size: var(--fs-sm);
    color: var(--text-muted);
}

.app-direccion-acciones { margin-block: var(--sp-2) 0; }

/* Enlace de acción dentro de un formulario (no navega, ejecuta). */
.app-btn-link {
    background: none;
    border: 0;
    padding: 0;
    font-size: var(--fs-sm);
    color: var(--color-action-fg);
    text-decoration: underline;
    cursor: pointer;
}
.app-btn-link:hover { text-decoration: none; }

/* ═════════════════════════════════════════════════════════════════════════
   16. LINEAS dinámicas (envíos: desglose multi-línea)
   ═════════════════════════════════════════════════════════════════════════ */
.app-lines { display: flex; flex-direction: column; gap: var(--sp-2); }

.app-line-item {
    background: var(--surface-soft);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    padding: var(--sp-2) var(--sp-3);
    display: flex;
    align-items: center;
    gap: var(--sp-2);
}

/* ═════════════════════════════════════════════════════════════════════════
   17. NAV DE AÑO (libros nacionales)
   ═════════════════════════════════════════════════════════════════════════ */
.app-year-nav {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-2);
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-pill);
    padding: var(--sp-1) var(--sp-2);
}

.app-year-nav-btn {
    background: transparent;
    border: none;
    color: var(--gray-700);
    width: 28px; height: 28px;
    border-radius: var(--radius-pill);
    cursor: pointer;
}

.app-year-nav-btn:hover:not(:disabled) {
    background: var(--surface-soft);
    color: var(--color-action-fg);
}

.app-year-nav-btn:disabled { opacity: 0.35; cursor: not-allowed; }

.app-year-nav-label {
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
    color: var(--color-action-fg);
    min-width: 60px;
    text-align: center;
}

/* ═════════════════════════════════════════════════════════════════════════
   18. LANDING (dashboard de inicio)
   El bloque legacy de .app-hero* que vivía aquí se ELIMINÓ: fijaba display:flex y
   padding en el propio .app-hero y, al no sobreescribirlos la versión canónica de
   más abajo, se filtraban → .app-hero-body se encogía al ancho del contenido (no
   llenaba la tarjeta) y había doble padding, por lo que el botón de logout no
   llegaba al borde derecho. La definición canónica del hero está en la sección
   "DASHBOARD DE INICIO" más abajo.
   ═════════════════════════════════════════════════════════════════════════ */

/* Grid/cards de módulos legacy ELIMINADOS: causaban un duplicado de .app-module-card
   (centrado) que se filtraba sobre la versión canónica del dashboard de más abajo.
   El dashboard usa ahora .app-rail + .app-module-card reestructurada (icono arriba-izq,
   flecha arriba-der). Las clases .app-module-icon/-title/-desc no se usaban en ninguna vista. */

/* ═════════════════════════════════════════════════════════════════════════
   19. PAGINADOR
   ═════════════════════════════════════════════════════════════════════════ */
.app-paginador {
    display: flex;
    justify-content: space-between;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--sp-2);
    padding: var(--sp-2) var(--sp-1) 0;
    font-size: var(--fs-sm);
}

.app-paginador-info { color: var(--gray-600); }

.app-paginador-btns {
    display: flex;
    gap: var(--sp-1);
    flex-wrap: wrap;
}

.app-paginador-btns button {
    min-width: 32px;
    padding: 0.18rem var(--sp-2);
    font-size: var(--fs-sm);
    border: 1px solid var(--border-strong);
    background: var(--surface-card);
    color: var(--color-action-fg);
    border-radius: var(--radius-md);
    cursor: pointer;
    transition: background var(--tr-fast);
}

.app-paginador-btns button:hover:not(:disabled):not(.active) {
    background: var(--surface-muted);
}

.app-paginador-btns button.active {
    background: var(--color-action);
    color: var(--text-on-action);
    border-color: var(--color-action);
    font-weight: var(--fw-semibold);
}

.app-paginador-btns button:disabled {
    opacity: 0.45;
    cursor: not-allowed;
}

.app-paginador-btns .ellipsis {
    min-width: 24px;
    text-align: center;
    color: var(--gray-500);
    align-self: center;
}

/* ═════════════════════════════════════════════════════════════════════════
   20. UTILIDADES PUNTUALES
   ═════════════════════════════════════════════════════════════════════════ */
.app-num { font-variant-numeric: tabular-nums; text-align: right; }
.app-truncate {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.app-divider {
    height: 1px;
    background: var(--border);
    margin: var(--sp-3) 0;
}

/* Tamaños tipográficos puntuales para celdas e iconos en línea. */
.app-cell--sm { font-size: var(--fs-sm); }
.app-icon-xs  { font-size: var(--fs-xs); }

/* ─────────────────────────────────────────────────────────────────────────
   Footer global (layout/footer.html)
   ───────────────────────────────────────────────────────────────────────── */
.app-footer {
    background: var(--gray-50);
    border-top: 1px solid var(--border);
    padding: var(--sp-1) var(--sp-4);
}
.app-footer-text { font-size: var(--fs-xs); }

/* ═════════════════════════════════════════════════════════════════════════
   DASHBOARD DE INICIO (templates/index.html)
   Hero con datos del usuario + grid de secciones por rol con cards.
   ═════════════════════════════════════════════════════════════════════════ */

.app-hero {
    background: var(--marca-grad);
    border-radius: var(--radius-2xl);
    box-shadow: var(--marca-sombra);
    overflow: hidden;
    position: relative;
}
.app-hero::before {
    content: '';
    position: absolute;
    top: -40px;
    right: -40px;
    width: 180px;
    height: 180px;
    background: var(--marca-deco);
    border-radius: 50%;
    pointer-events: none;
}
.app-hero-body {
    display: flex;
    align-items: center;
    gap: var(--sp-5);
    padding: var(--sp-4) var(--sp-5);
    position: relative;
}
/* El PNG va recortado al dibujo (sin margen transparente), así que el ancho del
   elemento ES el ancho visible del logo. El aire lo da el margin (no padding:
   con box-sizing: border-box el padding restaría tamaño al dibujo). */
.app-hero-logo {
    width: clamp(87px, 13.1vw, 204px);
    height: auto;
    margin: var(--sp-5);
    flex-shrink: 0;
}
.app-hero-info {
    flex: 1;
    min-width: 0;
}
.app-hero-greeting {
    font-size: var(--fs-xs);
    color: rgba(255, 255, 255, 0.7);
    margin: 0;
    text-transform: uppercase;
    letter-spacing: 0.5px;
}
.app-hero-name {
    font-size: var(--fs-lg);
    font-weight: var(--fw-bold);
    color: #ffffff;
    margin: var(--sp-1) 0 var(--sp-1);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.app-hero-badge {
    display: inline-flex;
    align-items: center;
    background: rgba(255, 255, 255, 0.2);
    color: white;
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
    padding: 0.22em 0.7em;
    border-radius: var(--radius-pill);
    text-transform: uppercase;
    letter-spacing: 0.4px;
    border: 1px solid rgba(255, 255, 255, 0.35);
}
.app-hero-meta {
    font-size: var(--fs-sm);
    color: rgba(255, 255, 255, 0.82);
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-1) var(--sp-5);
    margin-top: var(--sp-2);
}
.app-hero-actions {
    flex-shrink: 0;
    align-self: flex-start;   /* tema y cerrar sesión arriba a la derecha (como el mockup) */
    display: flex;
    align-items: center;
    gap: var(--sp-2);
}

/* Botones del hero (tema y cerrar sesión): translúcidos sobre el gradiente (patrón del mockup). */
.app-hero-btn {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-2);
    padding: var(--sp-3) var(--sp-4);
    border-radius: var(--radius-2xl);
    border: 1px solid rgba(255, 255, 255, 0.32);
    background: rgba(255, 255, 255, 0.13);
    color: #fff;
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
    cursor: pointer;
    transition: background var(--tr-fast);
}
.app-hero-btn:hover {
    background: rgba(255, 255, 255, 0.24);
    color: #fff;
}

@media (max-width: 575px) {
    .app-hero-body { padding: var(--sp-3) var(--sp-4); gap: var(--sp-3); }
    .app-hero-name { font-size: var(--fs-md); }
    .app-hero-meta { flex-direction: column; gap: var(--sp-1); }
}
@media (min-width: 768px) {
    .app-hero-body { padding: var(--sp-3) var(--sp-5); gap: var(--sp-5); }
    .app-hero-name { font-size: var(--fs-xl); }
}

/* Contenedor del dashboard de inicio */
.app-dashboard {
    height: 100%;
    display: flex;
    flex-direction: column;
    padding: var(--sp-2) var(--sp-3);
    max-width: 1600px;
    margin: 0 auto;
    overflow-y: hidden;
}
.app-dashboard-grid {
    display: flex;
    flex-direction: column;
    gap: var(--sp-2);
    flex: 1;
    overflow-y: auto;
}
.app-dashboard-section { flex-shrink: 0; }
/* Etiqueta de sección: chip de icono + título en versales + línea divisoria */
.app-section-label {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    margin-bottom: var(--sp-2);
}
.app-section-label-icon {
    --c: var(--color-action-fg);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    border-radius: var(--radius-xl);
    background: color-mix(in srgb, var(--c) 12%, transparent);
    color: var(--c);
    flex-shrink: 0;
}
.app-section-label-icon .app-icon { font-size: 1.1em; }
.app-section-label-text {
    font-size: var(--fs-sm);
    font-weight: var(--fw-bold);
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--gray-800);
}
.app-section-label-line {
    flex: 1;
    height: 1px;
    background: linear-gradient(90deg, var(--border-strong), transparent);
}
/* La sección Sistema es "app" (no eclesiástica): chip en neutro */
.app-section-label--system .app-section-label-icon { --c: var(--gray-600); }

/* Riel horizontal: pista con scroll-snap + flechas inteligentes (overflow en dashboard.js) */
.app-rail { position: relative; }
.app-rail-track {
    display: flex;
    gap: var(--sp-3);
    overflow-x: auto;
    scroll-snap-type: x proximity;
    scroll-behavior: smooth;
    padding: var(--sp-1) var(--sp-1) var(--sp-2);
    scrollbar-width: none;          /* estándar/Firefox */
}
.app-rail-track::-webkit-scrollbar { display: none; }   /* WebKit */

.app-rail-nav {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    z-index: 3;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 40px;
    height: 40px;
    border-radius: var(--radius-pill);
    border: 1px solid var(--border-strong);
    background: var(--surface-card);
    color: var(--color-action-fg);
    cursor: pointer;
    box-shadow: var(--shadow-md);
    transition: background var(--tr-fast), color var(--tr-fast), opacity var(--tr-fast);
}
.app-rail-nav:hover { background: var(--color-action); color: var(--text-on-action); }
/* Pegadas al borde del riel (flotan sobre la tarjeta del extremo); no se usan
   offsets negativos para no provocar scroll-x en .app-dashboard-grid. */
.app-rail-nav--prev { left: 0; }
.app-rail-nav--next { right: 0; }
.app-rail-nav.is-hidden { opacity: 0; pointer-events: none; }

/* Tarjeta de módulo: icono (arriba-izq) + flecha (arriba-der), título y subtítulo */
.app-module-card {
    --c: var(--color-action-fg);
    flex: none;
    width: 232px;
    scroll-snap-align: start;
    display: flex;
    flex-direction: column;
    align-items: stretch;
    justify-content: flex-start;
    gap: var(--sp-3);
    text-align: left;
    padding: var(--sp-3);
    border-radius: var(--radius-3xl);
    border: 1px solid var(--border);
    background: var(--surface-card);
    box-shadow: var(--shadow-sm);
    color: inherit;
    text-decoration: none;
    cursor: pointer;
    min-height: 148px;
    transition: transform var(--tr-base), box-shadow var(--tr-base), border-color var(--tr-base);
}
.app-module-card:hover {
    transform: translateY(-4px);
    box-shadow: var(--shadow-xl);
    border-color: var(--c);
    text-decoration: none;
}
.app-module-card:active { transform: translateY(-1px); }

.app-module-card-head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
}
.app-module-card-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 52px;
    height: 52px;
    border-radius: var(--radius-2xl);
    background: color-mix(in srgb, var(--c) 12%, transparent);
    color: var(--c);
    transition: background var(--tr-base), color var(--tr-base);
}
.app-module-card-icon .app-icon { font-size: 25px; }
.app-module-card:hover .app-module-card-icon {
    background: var(--c);
    color: var(--text-on-action);
}
.app-module-card-go {
    display: inline-flex;
    color: var(--c);
    opacity: 0;
    transform: translate(-6px, 6px);
    transition: opacity var(--tr-base), transform var(--tr-base);
}
.app-module-card:hover .app-module-card-go { opacity: 1; transform: translate(0, 0); }

.app-module-card-body { margin-top: auto; }
.app-module-card-title {
    font-size: var(--fs-md);
    font-weight: var(--fw-bold);
    color: var(--gray-800);
    margin: 0;
}
.app-module-card-desc {
    font-size: var(--fs-sm);
    color: var(--gray-600);
    margin: var(--sp-1) 0 0;
    line-height: 1.2;
}

/* Tematización por área (la sección Sistema va en neutro "app") */
.app-module-card--admin     { --c: var(--color-action-fg); }
.app-module-card--iglesia   { --c: var(--color-action-fg); }
.app-module-card--system    { --c: var(--gray-600); }
.app-module-card--tesoreria { --c: var(--color-action-fg); }
.app-module-card--danger    { --c: var(--danger); }   /* acción destructiva (cierre de libro) */

/* La tarjeta de módulo también se usa como <button> (arqueo/cierre): que herede
   tipografía y no arrastre los defaults del agente de usuario. */
button.app-module-card { font: inherit; }

/* ── Dashboards de tesorería (local y nacional) ───────────────────────────
   Reusan el layout del dashboard de inicio (.app-dashboard) con más aire, al
   estilo del de inicio: tarjetas CUADRADAS y compactas que NO se estiran a
   todo el ancho; se centran y el espacio sobrante queda como respiración.
   La banda de acción (Cierre / Reconciliación) va a todo el ancho debajo. */
.tesoreria-dashboard .app-dashboard-grid { gap: var(--sp-6); }   /* más aire entre RESUMEN y GESTIÓN */
.tesoreria-dashboard .app-section-label  { margin-bottom: var(--sp-3); }
.tesoreria-dashboard-stats { margin-bottom: 0; }

.tesoreria-gestion {
    flex: 1;
    min-height: 0;
    display: flex;
    flex-direction: column;
}
.tesoreria-module-grid {
    flex: 1;
    min-height: 0;
    display: flex;
    flex-wrap: wrap;
    justify-content: center;      /* las filas parciales quedan centradas */
    align-content: space-evenly;  /* filas repartidas en vertical: llenan el alto con aire */
    gap: var(--sp-5);
}
/* Tarjetas con la proporción del dashboard de inicio (tile, no rectángulo ancho),
   de tamaño fijo: llenan el ancho repartiéndose y el sobrante queda como aire. */
.tesoreria-module-grid .app-module-card {
    flex: 0 0 230px;
    width: 230px;
    aspect-ratio: 1.4;
    min-height: 0;
}
/* Banda de acción a todo el ancho, bajo la rejilla (Cierre local / Reconciliación nacional).
   Layout horizontal: icono · texto · chevron. El color lo da el modificador
   (--danger para Cierre, --tesoreria para Reconciliación). */
.tesoreria-action-banner {
    width: auto;                 /* suelta el ancho fijo del riel; se estira a todo el ancho */
    margin-top: var(--sp-3);
    flex-direction: row;
    align-items: center;
    gap: var(--sp-4);
    min-height: 0;
    padding-block: var(--sp-3);
}
.tesoreria-action-banner .app-module-card-body { margin-top: 0; flex: 1; }
.tesoreria-action-banner .app-module-card-go    { opacity: 1; transform: none; }

/* Variante "sparse" (pocas tarjetas, p. ej. nacional): la rejilla no se estira
   ni centra sus filas (queda pegada al rótulo) y la banda se ancla al fondo,
   en vez de centrar una única fila dejando un hueco bajo el rótulo. */
.tesoreria-dashboard--sparse .tesoreria-module-grid { flex: none; align-content: start; }
.tesoreria-dashboard--sparse .tesoreria-action-banner { margin-top: auto; }
/* Varias bandas seguidas (reconciliación + cierre en el nacional): solo la primera
   absorbe el hueco; las siguientes se apilan justo debajo con la separación normal. */
.tesoreria-dashboard--sparse .tesoreria-action-banner ~ .tesoreria-action-banner { margin-top: var(--sp-3); }

/* Tablet/estrecho: el flex-wrap reacomoda solo; permitir scroll si no cabe en alto */
@media (max-width: 991px) {
    .tesoreria-dashboard.app-dashboard { overflow-y: auto; }
}
/* Móvil: tarjetas algo más pequeñas para que entren 2 por fila */
@media (max-width: 575px) {
    .tesoreria-module-grid .app-module-card { flex-basis: 140px; width: 140px; }
}

@media (prefers-reduced-motion: reduce) {
    .app-module-card,
    .app-module-card:hover,
    .app-module-card:active { transform: none; }
    .app-rail-track { scroll-behavior: auto; }
    .app-module-card-go { transform: none; transition: opacity var(--tr-fast); }
}

@media (max-width: 767px) {
    .app-dashboard { padding: var(--sp-3); overflow-y: auto; }
    .app-dashboard-grid { gap: var(--sp-3); }
    .app-module-card { width: 210px; min-height: 132px; }
    .app-rail-nav { display: none; }   /* en táctil se navega con swipe */
}

/* ============================================================
   OVERLAY DE CARGA + TOASTS FIJOS (construidos por JS)
   Sustituyen estilos inline (z-index/min-width/backdrop) en
   libroDiario (mostrarSpinner / mostrarAlerta).
   ============================================================ */

/* Overlay de carga a pantalla completa con spinner centrado. */
.app-loading-overlay {
    position: fixed;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: var(--z-toast);
    background: var(--surface-backdrop);
}
.app-loading-overlay .spinner-border {
    inline-size: 3rem;
    block-size: 3rem;
}

/* ── Pila de toasts de notificar() (comun/helpers.js) ────────────────────────
   La pila es un popover: así entra al top-layer y se ve sobre el ::backdrop de
   un <dialog> modal. El UA centra los popover con `inset: 0; margin: auto` y
   les pone borde/fondo propios — hay que neutralizarlo todo aquí. */
.app-toast-stack[popover] {
    inset: auto;
    inset-block-end: var(--sp-3);
    inset-inline-end: var(--sp-3);
    margin: 0;
    padding: 0;
    border: 0;
    background: none;
    overflow: visible;
    display: flex;
    flex-direction: column;
    gap: var(--sp-2);
    inline-size: min(24rem, calc(100vw - var(--sp-6)));
}

.app-toast {
    display: flex;
    align-items: flex-start;
    gap: var(--sp-2);
    padding: var(--sp-3);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-floating);
    font-size: var(--fs-base);
    background: var(--info-soft);
    color: var(--info-soft-text);
}
.app-toast--success { background: var(--success-soft); color: var(--success-soft-text); }
.app-toast--warning { background: var(--warning-soft); color: var(--warning-soft-text); }
.app-toast--danger  { background: var(--danger-soft);  color: var(--danger-soft-text); }

.app-toast-text {
    flex: 1 1 auto;
    min-inline-size: 0;
    overflow-wrap: anywhere;   /* un mensaje de error largo no debe desbordar */
}

.app-toast-close {
    flex: none;
    background: none;
    border: 0;
    padding: 0;
    color: inherit;
    opacity: .7;
    cursor: pointer;
    line-height: 1;
}
.app-toast-close:hover { opacity: 1; }

/* Toast/alerta fija anclada arriba-centro (notificaciones de operación). */
.app-toast-fixed {
    position: fixed;
    top: var(--sp-3);
    left: 50%;
    transform: translateX(-50%);
    z-index: var(--z-toast);
    min-inline-size: 400px;
    max-inline-size: 600px;
}
/* Variante ancha para mensajes con acción (filtros que ocultan la fila). */
.app-toast-fixed--wide {
    min-inline-size: 500px;
    max-inline-size: 700px;
}

/* ═════════════════════════════════════════════════════════════════════════
   21. NAVBAR (barra superior · layout/header.html)

   Migrada desde Bootstrap (.navbar + .dropdown + utilidades + ~30 estilos
   inline con hex a pelo) al kit propio. Los dos paneles desplegables
   (notificaciones y cuenta) son `[popover]` nativos abiertos con
   `popovertarget`: viven en el top-layer, con light-dismiss y Escape del
   navegador. Se colocan bajo su botón con posicionarPopoverAnclado() en
   main.js — mismo criterio que los filtros de tabla, porque el anchor
   positioning CSS aún no está en Firefox.

   Dark mode: la BARRA conserva el gradiente corporativo en ambos temas (es
   identidad de marca; texto blanco sobre azul) y consume los tokens
   --navbar-*. Los PANELES son superficie normal y adaptan con --surface-card
   y la rampa de grises invertida.
   ═════════════════════════════════════════════════════════════════════════ */
.app-navbar {
    display: flex;
    align-items: center;
    flex-shrink: 0;
    padding: var(--sp-1) var(--sp-4);
    min-block-size: 48px;
    background: var(--navbar-bg);
    color: var(--navbar-fg);
}

.app-navbar-inner {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    inline-size: 100%;
    min-inline-size: 0;
}

/* El anillo de foco global es celeste, y sobre el azul del gradiente no llega a
   3:1 (WCAG 1.4.11). Dentro de la barra el foco se marca en blanco. */
.app-navbar :is(a, button):focus-visible {
    outline: 3px solid var(--navbar-fg);
    outline-offset: 2px;
}
/* Los paneles cuelgan del navbar en el DOM pero se pintan sobre superficie
   clara: ahí el blanco sería invisible, así que vuelven al anillo de siempre. */
.app-navbar-panel :is(a, button):focus-visible {
    outline-color: var(--ring-focus);
}

/* ── Marca ───────────────────────────────────────────────────────────── */
.app-navbar-brand {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-1);
    flex-shrink: 0;
    color: var(--navbar-fg);
    font-size: var(--fs-lg);
    font-weight: var(--fw-bold);
    text-decoration: none;
}
.app-navbar-brand:hover { color: var(--navbar-fg); text-decoration: none; }
.app-navbar-brand .app-icon { font-size: var(--fs-lg); }

.app-navbar-sep {
    inline-size: 1px;
    block-size: 20px;
    flex-shrink: 0;
    background: var(--navbar-divider);
}

/* ── Migas de pan ────────────────────────────────────────────────────── */
/* Implementación propia (ya no .breadcrumb de Bootstrap): el separador es un
   ::before del hermano, así no hay elemento vacío que leer para el lector. */
.app-navbar-crumbs {
    display: flex;
    align-items: center;
    flex: 1 1 auto;
    min-inline-size: 0;
    overflow: hidden;
}

.app-navbar-crumbs ol {
    display: flex;
    align-items: center;
    flex-wrap: nowrap;
    gap: var(--sp-1);
    min-inline-size: 0;
    margin: 0;
    padding: 0;
    list-style: none;
    font-size: var(--fs-sm);
}

.app-navbar-crumbs li {
    display: flex;
    align-items: center;
    gap: var(--sp-1);
    min-inline-size: 0;
}

.app-navbar-crumbs li + li::before {
    content: "/";
    color: var(--navbar-fg-subtle);
}

.app-navbar-crumbs a {
    color: var(--navbar-fg-muted);
    font-weight: var(--fw-semibold);
    text-decoration: none;
    white-space: nowrap;
}
.app-navbar-crumbs a:hover { color: var(--navbar-fg); text-decoration: underline; }

.app-navbar-crumbs [aria-current="page"] {
    max-inline-size: 200px;
    color: var(--navbar-fg-subtle);
}

/* Variante compacta de móvil: flecha al padre + página actual. */
.app-navbar-crumbs--compact { display: none; }
.app-navbar-crumbs--compact a { display: inline-flex; align-items: center; flex-shrink: 0; }
.app-navbar-crumbs--compact .app-navbar-crumb-actual {
    color: var(--navbar-fg);
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
}

@media (max-width: 575.98px) {
    .app-navbar { padding-inline: var(--sp-3); }
    .app-navbar-sep,
    .app-navbar-crumbs--full { display: none; }
    .app-navbar-crumbs--compact { display: flex; }
}

/* ── Botones de icono (tema, notificaciones) ─────────────────────────── */
.app-navbar-btn {
    position: relative;   /* ancla del badge de no leídas */
    display: inline-flex;
    align-items: center;
    justify-content: center;
    inline-size: 36px;
    block-size: 36px;
    flex-shrink: 0;
    padding: 0;
    border: none;
    border-radius: var(--radius-md);
    background: transparent;
    color: var(--navbar-fg-muted);
    cursor: pointer;
    transition: background var(--tr-fast), color var(--tr-fast);
}
.app-navbar-btn:hover { background: var(--navbar-hover-bg); color: var(--navbar-fg); }
.app-navbar-btn .app-icon { font-size: var(--fs-xl); }

/* Botón de tema (navbar e inicio): enseña el estado actual y anuncia la acción. Cuál
   de las dos opciones se ve lo decide data-theme, así el botón está al día desde el
   primer pintado sin esperar a ningún JS (comun/tema.js solo cambia el tema). */
.app-tema-opcion { display: inline-flex; align-items: center; }
:root:not([data-theme="dark"]) .app-tema-opcion--oscuro,
:root[data-theme="dark"] .app-tema-opcion--claro { display: none; }

.app-navbar-badge {
    position: absolute;
    inset-block-start: 1px;
    inset-inline-end: 1px;
    min-inline-size: 16px;
    padding: 0.1em 0.35em;
    border-radius: var(--radius-pill);
    background: var(--danger);
    color: var(--text-on-action);
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
    line-height: 1.3;
}
.app-navbar-badge[hidden] { display: none; }

/* ── Disparador de cuenta ────────────────────────────────────────────── */
.app-navbar-user {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-1);
    flex-shrink: 0;
    min-inline-size: 0;
    padding: var(--sp-1);
    border: none;
    border-radius: var(--radius-md);
    background: transparent;
    color: var(--navbar-fg);
    font-size: var(--fs-base);
    font-weight: var(--fw-semibold);
    cursor: pointer;
    transition: background var(--tr-fast);
}
.app-navbar-user:hover { background: var(--navbar-hover-bg); }

.app-navbar-avatar {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    inline-size: 30px;
    block-size: 30px;
    flex-shrink: 0;
    border-radius: var(--radius-pill);
    background: var(--navbar-fg);
    color: var(--ipue-azul);
}
.app-navbar-avatar .app-icon { font-size: var(--fs-md); }

.app-navbar-user-name { max-inline-size: 120px; }

@media (max-width: 767.98px) {
    .app-navbar-user-name { display: none; }
}

/* ── Paneles desplegables (popover nativo) ───────────────────────────── */
.app-navbar-panel {
    inline-size: 300px;
    max-inline-size: min(95vw, 360px);
    max-block-size: 380px;
    overflow-y: auto;
    margin: 0;
    inset: auto;       /* la posición la fija main.js bajo el botón */
    padding: 0;
    border: 1px solid var(--border-default);
    border-radius: var(--radius-xl);
    background: var(--surface-card);
    color: var(--text-default);
    box-shadow: var(--shadow-floating);
}
.app-navbar-panel--user { inline-size: 250px; max-inline-size: min(90vw, 300px); }

.app-navbar-panel-head {
    position: sticky;   /* la lista scrollea por detrás */
    top: 0;
    z-index: 1;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-2);
    padding: var(--sp-2) var(--sp-3);
    border-bottom: 1px solid var(--border-default);
    background: var(--surface-muted);
    color: var(--text-strong);
    font-size: var(--fs-sm);
    font-weight: var(--fw-bold);
}

.app-navbar-panel-link {
    padding: 0;
    border: none;
    background: none;
    color: var(--text-muted);
    font-size: var(--fs-xs);
    cursor: pointer;
}
.app-navbar-panel-link:hover { color: var(--color-action-fg); text-decoration: underline; }

.app-navbar-panel-foot {
    padding: var(--sp-2) var(--sp-3) var(--sp-3);
    border-top: 1px solid var(--border-subtle);
}
.app-navbar-panel-foot .app-btn { inline-size: 100%; justify-content: center; }

/* ── Panel de cuenta ─────────────────────────────────────────────────── */
.app-navbar-user-head {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    padding: var(--sp-2) var(--sp-3);
    border-bottom: 1px solid var(--border-default);
    background: var(--surface-muted);
}

.app-navbar-user-avatar {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    inline-size: 34px;
    block-size: 34px;
    flex-shrink: 0;
    border-radius: var(--radius-pill);
    background: var(--color-action);
    color: var(--text-on-action);
}

.app-navbar-user-ident { min-inline-size: 0; }
.app-navbar-user-nombre {
    color: var(--text-strong);
    font-size: var(--fs-base);
    font-weight: var(--fw-bold);
}
.app-navbar-user-rol {
    display: flex;
    align-items: center;
    gap: var(--sp-1);
    color: var(--text-muted);
    font-size: var(--fs-xs);
}

.app-navbar-meta { padding: var(--sp-1) var(--sp-3); font-size: var(--fs-sm); }
.app-navbar-meta:first-of-type { padding-block-start: var(--sp-2); }
.app-navbar-meta-label {
    display: flex;
    align-items: center;
    gap: var(--sp-1);
    color: var(--text-muted);
    font-weight: var(--fw-semibold);
}
.app-navbar-meta-label .app-icon { color: var(--color-action-fg); font-size: var(--fs-base); }
.app-navbar-meta-value { padding-inline-start: var(--sp-5); color: var(--text-strong); }

/* ── Lista de notificaciones ─────────────────────────────────────────── */
.app-notif-list { margin: 0; padding: 0; list-style: none; }

.app-notif-item {
    display: flex;
    align-items: flex-start;
    gap: var(--sp-2);
    padding: var(--sp-2) var(--sp-3);
    border-bottom: 1px solid var(--border-subtle);
    color: inherit;
    text-decoration: none;
    transition: background var(--tr-fast);
}
.app-notif-item:hover { background: var(--surface-muted); color: inherit; }
.app-notif-item--unread { background: var(--color-action-soft); }

.app-notif-icon { flex-shrink: 0; margin-block-start: 2px; color: var(--color-action-fg); }
.app-notif-icon--danger  { color: var(--danger); }
.app-notif-icon--success { color: var(--success); }
.app-notif-icon--warning { color: var(--warning); }
.app-notif-item--read .app-notif-icon { color: var(--text-subtle); }

.app-notif-body { flex: 1; min-inline-size: 0; }
.app-notif-top {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-2);
}
.app-notif-title {
    color: var(--text-strong);
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
}
.app-notif-item--read .app-notif-title { color: var(--text-muted); font-weight: var(--fw-medium); }
.app-notif-msg { margin: 0; color: var(--text-muted); font-size: var(--fs-sm); }
.app-notif-time { color: var(--text-subtle); font-size: var(--fs-xs); }

.app-notif-empty {
    padding: var(--sp-5) var(--sp-3);
    color: var(--text-muted);
    font-size: var(--fs-sm);
    text-align: center;
}

/* Pista de formulario en tono de aviso: algo que conviene mirar pero que no
   impide guardar (p. ej. un socio que quiza ya esta en la membresia). */
.app-form-hint--warn {
    color: var(--warning-soft-text);
    font-weight: var(--fw-medium);
}

/* ═════════════════════════════════════════════════════════════════════════
   22. LOGIN (login/CustomLogin.html)
   Tarjeta compacta y centrada, partida por dentro: la marca a la izquierda (el
   degradado del héroe del inicio, --marca-*) y el formulario a la derecha. El
   fondo lleva un resplandor suave en los azules de marca para que la tarjeta
   destaque sin efectos que estorben el contraste. Por debajo de 768px la tarjeta
   es de una columna y la marca una franja: los campos y el botón quedan arriba y
   el teclado no los tapa (modern-web-guidance: autofill-sign-in-form).
   ═════════════════════════════════════════════════════════════════════════ */
.app-login {
    min-block-size: 100dvb;
    display: grid;
    place-items: center;
    padding: var(--sp-5) var(--sp-4);
    background:
        radial-gradient(48rem 36rem at 0% 0%, color-mix(in oklab, var(--ipue-celeste) 16%, transparent), transparent 70%),
        radial-gradient(48rem 36rem at 100% 100%, color-mix(in oklab, var(--ipue-azul) 14%, transparent), transparent 70%),
        var(--surface-page);
    color: var(--text-default);
}
.app-login-contenedor {
    inline-size: min(100%, 58rem);
    display: flex;
    flex-direction: column;
    gap: var(--sp-3);
}
.app-login-tarjeta {
    display: grid;
    grid-template-columns: minmax(0, 0.95fr) minmax(0, 1.05fr);
    overflow: hidden;
    border: 1px solid var(--border-default);
    border-radius: var(--radius-3xl);
    background: var(--surface-card);
    box-shadow: var(--shadow-floating);
}

/* ── Marca ── */
/* Solo el logo, centrado en todo el panel: es una app privada y quien entra sabe a
   qué viene. El nombre y la versión van fijos arriba para no descentrarlo. */
.app-login-marca {
    position: relative;
    isolation: isolate;
    overflow: hidden;
    display: grid;
    place-items: center;
    min-block-size: 22rem;
    padding: var(--sp-6);
    background: var(--marca-grad);
    color: var(--marca-fg);
}
/* Círculos translúcidos: el mismo recurso que el héroe del inicio */
.app-login-marca::before,
.app-login-marca::after {
    content: '';
    position: absolute;
    z-index: -1;
    border-radius: 50%;
    background: var(--marca-deco);
    pointer-events: none;
}
.app-login-marca::before {
    inline-size: 18rem;
    block-size: 18rem;
    inset-block-start: -7rem;
    inset-inline-end: -6rem;
}
.app-login-marca::after {
    inline-size: 12rem;
    block-size: 12rem;
    inset-block-end: -5rem;
    inset-inline-start: -4rem;
}
.app-login-marca-cabecera {
    position: absolute;
    inset-block-start: var(--sp-5);
    inset-inline: var(--sp-6);
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-3);
}
.app-login-nombre {
    margin: 0;
    font-size: var(--fs-lg);
    font-weight: var(--fw-bold);
    letter-spacing: 0.02em;
}
/* Pastilla de cristal sobre el degradado */
.app-login-version {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-1);
    padding: var(--sp-1) var(--sp-2);
    border: 1px solid var(--marca-glass-borde);
    border-radius: var(--radius-pill);
    background: var(--marca-glass);
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
}
.app-login-logo {
    inline-size: min(100%, 17rem);
    block-size: auto;
    aspect-ratio: 640 / 454;
}

/* ── Formulario ── */
.app-login-acceso {
    display: grid;
    align-items: center;
    padding: clamp(var(--sp-6), 4vw, var(--sp-8));
}
.app-login-formulario {
    display: flex;
    flex-direction: column;
    gap: var(--sp-5);
}
.app-login-titulo {
    margin: 0;
    color: var(--text-strong);
    font-size: clamp(var(--fs-2xl), 1.1rem + 0.8vw, var(--fs-3xl));
    font-weight: var(--fw-bold);
    text-wrap: balance;
}
.app-login-subtitulo {
    margin: var(--sp-2) 0 0;
    color: var(--text-muted);
    font-size: var(--fs-md);
    text-wrap: pretty;
}
.app-login .app-alert {
    margin: 0;
    padding: var(--sp-3);
    font-size: var(--fs-sm);
}
/* En el login un error no tiene que latir: se lee y se corrige */
.app-login .app-alert--danger { animation: none; }

.app-login-campos {
    display: flex;
    flex-direction: column;
    gap: var(--sp-4);
    margin: 0;
}
.app-login-etiqueta {
    display: block;
    margin-block-end: var(--sp-1);
    color: var(--text-strong);
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
}
.app-login-campo { position: relative; }
/* Icono delante del texto (persona, candado): decorativo */
.app-login-campo > .app-icon {
    position: absolute;
    inset-inline-start: var(--sp-3);
    inset-block-start: 50%;
    translate: 0 -50%;
    color: var(--text-subtle);
    font-size: var(--fs-xl);
    pointer-events: none;
}
.app-login-input {
    inline-size: 100%;
    block-size: 3rem;
    padding-inline: calc(var(--sp-3) + 1.75rem) var(--sp-3);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-xl);
    background: var(--surface-card);
    color: var(--text-strong);
    font: inherit;
    font-size: 1rem;   /* ≥16px: iOS no hace zoom al enfocar */
    transition: border-color var(--tr-fast), box-shadow var(--tr-fast);
}
.app-login-input--con-boton { padding-inline-end: 3rem; }
.app-login-input:focus-visible {
    outline: none;
    border-color: var(--color-action-fg);
    box-shadow: 0 0 0 3px color-mix(in oklab, var(--ring-focus) 35%, transparent);
}
/* Solo después de tocar el campo o intentar enviar, nunca al cargar */
.app-login-input:user-invalid {
    border-color: var(--danger);
    box-shadow: 0 0 0 3px color-mix(in oklab, var(--danger) 20%, transparent);
}

/* Botón de mostrar/ocultar la contraseña. El icono lo elige aria-pressed. */
.app-login-ver {
    position: absolute;
    inset-inline-end: var(--sp-1);
    inset-block-start: 50%;
    translate: 0 -50%;
    display: grid;
    place-items: center;
    inline-size: 2.5rem;
    block-size: 2.5rem;
    border: 0;
    border-radius: var(--radius-lg);
    background: transparent;
    color: var(--text-muted);
    cursor: pointer;
}
.app-login-ver:hover { background: var(--surface-muted); color: var(--text-strong); }
.app-login-ver[aria-pressed="true"] .app-login-ver-mostrar,
.app-login-ver:not([aria-pressed="true"]) .app-login-ver-ocultar { display: none; }

/* Aviso de Bloq Mayús: la causa más común de «contraseña incorrecta» */
.app-login-mayus {
    display: flex;
    align-items: center;
    gap: var(--sp-1);
    margin: var(--sp-2) 0 0;
    color: var(--warning-soft-text);
    font-size: var(--fs-sm);
    font-weight: var(--fw-medium);
}
.app-login-mayus[hidden] { display: none; }

.app-login-enviar {
    inline-size: 100%;
    min-block-size: 3rem;
    gap: var(--sp-2);
    border-radius: var(--radius-xl);
    font-size: var(--fs-md);
}
/* Mientras se envía: ruedita en lugar de la flecha (login.js pone aria-busy) */
.app-login-spinner {
    display: none;
    inline-size: 1.1em;
    block-size: 1.1em;
    border: 2px solid color-mix(in oklab, currentColor 35%, transparent);
    border-block-start-color: currentColor;
    border-radius: 50%;
    animation: app-login-girar 0.8s linear infinite;
}
.app-login-enviar[aria-busy="true"] { cursor: progress; }
.app-login-enviar[aria-busy="true"] .app-login-spinner { display: inline-block; }
.app-login-enviar[aria-busy="true"] .app-login-enviar-flecha { display: none; }
@keyframes app-login-girar { to { rotate: 1turn; } }
@media (prefers-reduced-motion: reduce) {
    .app-login-spinner { animation-duration: 2.4s; }
}

.app-login-ayuda {
    margin: 0;
    color: var(--text-muted);
    font-size: var(--fs-sm);
    text-align: center;
    text-wrap: pretty;
}

/* ── Alrededor de la tarjeta: tema, idioma y pie ── */
.app-login-herramientas {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: var(--sp-2);
}
.app-login-pie {
    margin: 0;
    color: var(--text-subtle);
    font-size: var(--fs-xs);
    text-align: center;
}
.app-login-icono-btn {
    display: inline-grid;
    place-items: center;
    inline-size: 2.5rem;
    block-size: 2.5rem;
    border: 1px solid var(--border-default);
    border-radius: var(--radius-pill);
    background: var(--surface-card);
    color: var(--text-muted);
    cursor: pointer;
}
.app-login-icono-btn:hover { border-color: var(--border-strong); color: var(--text-strong); }
.app-login-idiomas {
    display: inline-flex;
    gap: var(--sp-1);
    padding: var(--sp-1);
    border: 1px solid var(--border-default);
    border-radius: var(--radius-pill);
    background: var(--surface-card);
}
.app-login-idioma {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-1);
    padding: var(--sp-1) var(--sp-3);
    border-radius: var(--radius-pill);
    color: var(--text-muted);
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    text-decoration: none;
}
.app-login-idioma:hover { color: var(--text-strong); }
.app-login-idioma[aria-current="true"] {
    background: var(--color-action-soft);
    color: var(--color-action-fg);
}
/* Contorno fino: la bandera polaca es blanca arriba y se fundiría con el fondo */
.app-login-idioma img {
    border-radius: var(--radius-sm);
    box-shadow: 0 0 0 1px var(--border-default);
}

/* ── Móvil: una columna, la marca en franja y el formulario arriba ── */
@media (max-width: 767.98px) {
    .app-login {
        place-items: start center;
        padding: var(--sp-3);
    }
    .app-login-tarjeta { grid-template-columns: minmax(0, 1fr); }
    .app-login-marca {
        display: flex;
        flex-direction: row-reverse;
        align-items: center;
        justify-content: flex-end;
        gap: var(--sp-3);
        min-block-size: 0;
        padding: var(--sp-3) var(--sp-4);
    }
    .app-login-marca-cabecera {
        position: static;
        flex-direction: column;
        align-items: flex-start;
        gap: var(--sp-1);
    }
    .app-login-logo { inline-size: 4.5rem; }
    .app-login-marca::after { display: none; }
    .app-login-marca::before {
        inline-size: 10rem;
        block-size: 10rem;
        inset-block-start: -5rem;
    }
    .app-login-acceso { padding: var(--sp-5) var(--sp-4); }
    /* Tema e idioma, debajo: lo primero en pantalla es el formulario */
    .app-login-herramientas { order: 2; justify-content: center; }
    .app-login-pie { order: 3; }
}
