:root {
  --app-bg: #f5f7fb;
  --app-surface: #ffffff;
  --app-surface-soft: #f8fafc;
  --app-border: #e2e8f0;
  --app-border-soft: #eef2f7;
  --app-text: #0f172a;
  /*
   * Colores semanticos: TODOS pasan AA (4,5:1) sobre blanco y sobre --app-bg.
   *
   * Cuatro no llegaban, y son justo los que dicen si el dinero esta bien o mal:
   *   --app-success  #059669 -> 3,77  (el verde de "pagado")
   *   --app-warning  #d97706 -> 3,19  (el ambar de "vencido")
   *   --app-info     #0284c7 -> 4,10
   *   --app-muted    #64748b -> 4,44 sobre --app-bg
   *
   * Importa mas de lo normal aqui: buena parte de los propietarios son adultos
   * mayores leyendo en un telefono, muchas veces a la luz del dia. Los valores
   * nuevos son el escalon siguiente de la misma escala (700 en vez de 600), asi
   * que la interfaz se ve igual, solo que legible.
   *
   * Si se cambia alguno, comprobar el contraste ANTES: sobre #ffffff y sobre
   * var(--app-bg), que es el fondo real de casi todas las pantallas.
   */
  --app-muted: #55637a;    /* 5,67 sobre --app-bg (antes 4,44) */
  --app-primary: #2563eb;  /* 5,17 sobre blanco */
  --app-success: #047857;  /* 5,48 sobre blanco (antes 3,77) */
  --app-warning: #b45309;  /* 5,02 sobre blanco (antes 3,19) */
  --app-danger: #dc2626;   /* 4,83 sobre blanco */
  --app-info: #0369a1;     /* 5,93 sobre blanco (antes 4,10) */
  --card-radius: 14px;
  --app-shadow: 0 10px 28px rgba(15, 23, 42, 0.06);
  --app-shadow-sm: 0 4px 14px rgba(15, 23, 42, 0.05);

  /* Acento de marca unico: Bootstrap adopta el azul #2563eb */
  --bs-primary: #2563eb;
  --bs-primary-rgb: 37, 99, 235;
  --bs-link-color: #2563eb;
  --bs-link-color-rgb: 37, 99, 235;
  --bs-link-hover-color: #1d4ed8;
}

/* El fondo va tambien en `html`, no solo en `body`. Entre una pagina y la
   siguiente el navegador pinta el lienzo de la raiz antes de tener el body
   listo: sin esto, cada navegacion metia un fotograma blanco -el "parpadeo"
   que se sentia como refresco violento, sobre todo en movil contra las barras
   oscuras-. */
html {
  background-color: var(--app-bg);
}

body {
  font-family: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  background:
    radial-gradient(circle at top right, rgba(37, 99, 235, 0.08), transparent 32rem),
    var(--app-bg) !important;
  color: var(--app-text);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  letter-spacing: -0.01em;
}

h1, h2, h3, h4, h5, h6,
.h1, .h2, .h3, .h4, .h5, .h6 {
  font-family: "Inter", system-ui, sans-serif;
  letter-spacing: -0.02em;
}

h1, h2, h3, .h1, .h2, .h3 {
  font-weight: 800;
  text-wrap: balance;
}

/* Cifras alineadas (financiero): figuras tabulares en datos */
.table,
.metric-value,
.kpi,
.mono,
.font-monospace {
  font-variant-numeric: tabular-nums;
}

.container,
.container-lg,
.container-md,
.container-sm,
.container-xl,
.container-xxl {
  max-width: 1320px;
}

.container-fluid {
  max-width: none;
}

.card,
.modal-content,
.dropdown-menu {
  border-radius: var(--card-radius);
}

.card {
  border: 1px solid var(--app-border);
  box-shadow: var(--app-shadow-sm);
}

.card-header {
  border-bottom-color: var(--app-border-soft);
}

/* ==========================================================================
   BOTONES
   ==========================================================================

   TRES TONOS, UNA SOLA FORMA

     solido  (.btn-primary, .btn-danger, .btn-success, .btn-dark)
             La accion principal de la pantalla. Como mucho una por barra.

     suave   (.btn-outline-primary / -danger / -success / -warning / -info)
             Fondo tintado del propio color y tinta del mismo color, sin borde
             duro. Es una accion secundaria que SE VE, y que dice de que va por
             el color sin gritar como un boton solido.

     neutro  (.btn-outline-secondary, .btn-outline-dark)
             Superficie blanca, borde estandar y texto en tinta. Lo que no
             tiene color porque no lo necesita: exportar, cancelar, volver.

   POR QUE EL "OUTLINE" DEJO DE SER UN CONTORNO

   Un boton de contorno -fondo transparente y una linea de 1px del color- es
   casi invisible sobre una tarjeta blanca, y en una barra con tres seguidos
   los tres pesaban igual: no habia forma de saber cual era la accion normal y
   cual la que borra. El fondo tintado devuelve superficie al boton, y el color
   pasa a significar algo (rojo = destruye, azul = envia) en vez de ser el
   grosor de una linea.

   EL CONTRASTE DEL TEXTO NO ES EL DEL TOKEN

   Los tokens --app-* estan calibrados para pasar 4,5:1 SOBRE BLANCO, y varios
   van justos (--app-danger da 4,83). En cuanto el fondo se tinta, ese margen
   desaparece: #dc2626 sobre su propio tinte al 8% cae a 4,11:1 y el boton se
   vuelve ilegible para la mitad de los propietarios, que son adultos mayores
   leyendo en un telefono al sol.

   Por eso la tinta NO es el token: es el token oscurecido lo justo para que el
   peor de los tres estados (reposo 8%, hover 14%, pulsado 20%) siga pasando
   4,5:1, comprobado sobre tarjeta blanca y sobre --app-bg. Los porcentajes de
   cada tono estan calculados, no elegidos a ojo; si se toca un token, hay que
   recalcularlos.
   ========================================================================== */

.btn {
  /* Flex y no inline-block: el hueco entre el icono y el texto lo pone `gap`,
     de una vez para toda la app. Antes lo ponia cada vista con me-1 o me-2, y
     dos botones vecinos acababan con separaciones distintas. */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: .5rem;
  border-radius: 10px;
  font-weight: 700;
  padding: .55rem 1.05rem;
  transition: background-color .18s ease, border-color .18s ease, color .18s ease, box-shadow .18s ease, transform .18s ease;
}

/* Las utilidades me-1, ms-2 y companeras de los iconos se anulan: con `gap`
   puesto ya no separan, solo descuadran. Las variantes `auto` (me-auto,
   ms-auto) se respetan, que esas si colocan de verdad. */
.btn > .bi.me-1, .btn > .bi.me-2, .btn > .bi.me-3,
.btn > .bi.ms-1, .btn > .bi.ms-2, .btn > .bi.ms-3 {
  margin-right: 0 !important;
  margin-left: 0 !important;
}

/* El icono, un punto por encima del texto: es lo que hace que se lea como un
   boton con icono y no como texto con un simbolo delante. */
.btn > .bi {
  font-size: 1.08em;
  line-height: 1;
}

/* El pequeno no es el boton estrecho: es el boton de una barra de herramientas,
   que sigue siendo la accion de una pantalla y necesita area de toque. Solo
   baja el cuerpo de letra; el aire se conserva.

   Dentro de un `.input-group-sm` Bootstrap gana por especificidad y lo vuelve
   a comprimir, que es lo que hay que hacer ahi: pegado a un campo, el boton
   tiene que medir lo que el campo. */
.btn-sm {
  border-radius: 10px;
  padding: .5rem .95rem;
  font-size: .82rem;
  gap: .45rem;
}

.btn-lg {
  border-radius: 12px;
  padding: .75rem 1.4rem;
}

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

.btn:focus-visible {
  outline: none;
  box-shadow: 0 0 0 .22rem var(--btn-ring, rgba(37, 99, 235, 0.28));
}

/* --------------------------------------------------------------- solidos -- */

/* Acento de marca: unifica primary de Bootstrap al azul #2563eb en toda la app */
.btn-primary {
  --bs-btn-bg: #2563eb;
  --bs-btn-border-color: #2563eb;
  --bs-btn-hover-bg: #1d4ed8;
  --bs-btn-hover-border-color: #1d4ed8;
  --bs-btn-active-bg: #1d4ed8;
  --bs-btn-active-border-color: #1d4ed8;
  --bs-btn-disabled-bg: #2563eb;
  --bs-btn-disabled-border-color: #2563eb;
  --btn-ring: color-mix(in srgb, var(--app-primary) 35%, transparent);
}

/* Bootstrap trae su propio rojo (#dc3545) y su propio verde, distintos de los
   del sistema: el mismo "peligro" salia de dos colores segun el boton fuera
   solido o suave. Se pinan a los tokens. */
.btn-danger {
  --bs-btn-bg: var(--app-danger);
  --bs-btn-border-color: var(--app-danger);
  --bs-btn-hover-bg: color-mix(in srgb, var(--app-danger) 86%, #000);
  --bs-btn-hover-border-color: color-mix(in srgb, var(--app-danger) 86%, #000);
  --bs-btn-active-bg: color-mix(in srgb, var(--app-danger) 78%, #000);
  --bs-btn-active-border-color: color-mix(in srgb, var(--app-danger) 78%, #000);
  --bs-btn-disabled-bg: var(--app-danger);
  --bs-btn-disabled-border-color: var(--app-danger);
  --btn-ring: color-mix(in srgb, var(--app-danger) 35%, transparent);
}

.btn-success {
  --bs-btn-bg: var(--app-success);
  --bs-btn-border-color: var(--app-success);
  --bs-btn-hover-bg: color-mix(in srgb, var(--app-success) 86%, #000);
  --bs-btn-hover-border-color: color-mix(in srgb, var(--app-success) 86%, #000);
  --bs-btn-active-bg: color-mix(in srgb, var(--app-success) 78%, #000);
  --bs-btn-active-border-color: color-mix(in srgb, var(--app-success) 78%, #000);
  --bs-btn-disabled-bg: var(--app-success);
  --bs-btn-disabled-border-color: var(--app-success);
  --btn-ring: color-mix(in srgb, var(--app-success) 35%, transparent);
}

/* ----------------------------------------------------------------- suaves -- */

.btn-outline-primary,
.btn-outline-success,
.btn-outline-danger,
.btn-outline-warning,
.btn-outline-info {
  /* El fondo se mezcla con `transparent`, no con blanco: asi el mismo boton
     funciona sobre la tarjeta, sobre el fondo de pagina y sobre el pie gris de
     un modal sin tener que declarar tres variantes. */
  --bs-btn-bg: color-mix(in srgb, var(--btn-tono) 8%, transparent);
  --bs-btn-border-color: color-mix(in srgb, var(--btn-tono) 14%, transparent);
  --bs-btn-color: var(--btn-tinta);

  --bs-btn-hover-bg: color-mix(in srgb, var(--btn-tono) 14%, transparent);
  --bs-btn-hover-border-color: color-mix(in srgb, var(--btn-tono) 24%, transparent);
  --bs-btn-hover-color: var(--btn-tinta);

  --bs-btn-active-bg: color-mix(in srgb, var(--btn-tono) 20%, transparent);
  --bs-btn-active-border-color: color-mix(in srgb, var(--btn-tono) 30%, transparent);
  --bs-btn-active-color: var(--btn-tinta);

  --bs-btn-disabled-bg: color-mix(in srgb, var(--btn-tono) 6%, transparent);
  --bs-btn-disabled-border-color: transparent;
  --bs-btn-disabled-color: var(--btn-tinta);

  --btn-ring: color-mix(in srgb, var(--btn-tono) 30%, transparent);
}

/* Las mezclas hacia negro salen del calculo de contraste descrito arriba:
   son el minimo que mantiene 4,5:1 en los tres estados. */
.btn-outline-primary { --btn-tono: var(--app-primary); --btn-tinta: color-mix(in srgb, var(--app-primary) 86%, #000); }
.btn-outline-success { --btn-tono: var(--app-success); --btn-tinta: color-mix(in srgb, var(--app-success) 90%, #000); }
.btn-outline-warning { --btn-tono: var(--app-warning); --btn-tinta: color-mix(in srgb, var(--app-warning) 86%, #000); }
.btn-outline-danger  { --btn-tono: var(--app-danger);  --btn-tinta: color-mix(in srgb, var(--app-danger) 82%, #000); }
.btn-outline-info    { --btn-tono: var(--app-info);    --btn-tinta: color-mix(in srgb, var(--app-info) 94%, #000); }

/* ---------------------------------------------------------------- neutro -- */

.btn-outline-secondary,
.btn-outline-dark {
  --bs-btn-color: var(--app-text);
  --bs-btn-bg: var(--app-surface);
  --bs-btn-border-color: var(--app-border);

  --bs-btn-hover-color: var(--app-text);
  --bs-btn-hover-bg: var(--app-surface-soft);
  --bs-btn-hover-border-color: color-mix(in srgb, var(--app-muted) 40%, var(--app-border));

  --bs-btn-active-color: var(--app-text);
  --bs-btn-active-bg: var(--app-border-soft);
  --bs-btn-active-border-color: color-mix(in srgb, var(--app-muted) 50%, var(--app-border));

  --bs-btn-disabled-color: var(--app-muted);
  --bs-btn-disabled-bg: var(--app-surface);
  --bs-btn-disabled-border-color: var(--app-border);

  --btn-ring: color-mix(in srgb, var(--app-muted) 28%, transparent);
}

/* El icono va un tono por debajo del texto: en un boton sin color, es lo que
   deja que la etiqueta mande y el simbolo acompane. */
.btn-outline-secondary > .bi,
.btn-outline-dark > .bi {
  color: var(--app-muted);
  transition: color .18s ease;
}

.btn-outline-secondary:hover > .bi,
.btn-outline-dark:hover > .bi {
  color: var(--app-text);
}

@media (prefers-reduced-motion: reduce) {
  .btn { transition: none; }
  .btn:active { transform: none; }
}

.text-primary { color: #2563eb !important; }
/* Respeta las utilidades .bg-opacity-* de Bootstrap: sin ellas queda sólido (opacidad 1). */
.bg-primary { background-color: rgba(37, 99, 235, var(--bs-bg-opacity, 1)) !important; }
.link-primary { color: #2563eb !important; }

.form-control,
.form-select {
  border-color: var(--app-border);
  border-radius: 10px;
}

.form-control:focus,
.form-select:focus {
  border-color: rgba(37, 99, 235, 0.45);
  box-shadow: 0 0 0 0.22rem rgba(37, 99, 235, 0.12);
}

.badge {
  letter-spacing: 0;
}

/* Chip de unidad (apartamento / local). Va pegado al nombre del edificio en los
   listados, y tiene que LEERSE: es el dato que dice de que puerta se habla.
   Existe porque se estaba resolviendo con `.badge.bg-secondary.opacity-25`, y
   `opacity` no destine el fondo: atenua el elemento entero, texto incluido. El
   numero quedaba a un 25% sobre blanco -por debajo de cualquier minimo de
   contraste legible- y en la practica no se veia. El tono suave se consigue con
   el color del fondo, no bajandole la opacidad al contenido. */
.unit-chip {
  display: inline-block;
  padding: .1rem .45rem;
  border-radius: 6px;
  border: 1px solid var(--app-border);
  background: var(--app-surface-soft);
  color: var(--app-text);
  font-size: .72rem;
  font-weight: 700;
  line-height: 1.4;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

.mono,
.kpi {
  font-family: "SF Mono", "Roboto Mono", Menlo, Consolas, monospace;
}

.kpi {
  font-size: 2rem;
  font-weight: 800;
}

.app-page {
  padding-top: 1.5rem;
  padding-bottom: 2rem;
}

/* Hero claro, alineado al del dashboard: superficie blanca con tinte azul
   sutil, texto en tinta y acentos en el azul de marca (nada de gradiente
   oscuro ni texto blanco). */
.app-hero {
  background:
    linear-gradient(135deg, rgba(255, 255, 255, .98), rgba(239, 246, 255, .94)),
    radial-gradient(circle at right top, rgba(37, 99, 235, .16), transparent 28rem);
  color: var(--app-text);
  border: 1px solid #dbeafe;
  border-radius: 18px;
  padding: 1.35rem;
  box-shadow: 0 10px 28px rgba(15, 23, 42, .07);
  overflow: hidden;
  position: relative;
}

.app-hero::after {
  content: "";
  position: absolute;
  inset: auto -7rem -9rem auto;
  width: 18rem;
  height: 18rem;
  background: rgba(37, 99, 235, .08);
  border-radius: 999px;
}

.app-hero > * {
  position: relative;
  z-index: 1;
}

.app-hero-title {
  color: var(--app-text);
  font-size: clamp(1.35rem, 2vw, 2rem);
  font-weight: 850;
  margin: 0;
}

.app-hero-subtitle {
  color: var(--app-muted);
  margin: .25rem 0 0;
}

.glass-pill {
  background: var(--app-surface);
  border: 1px solid #dbeafe;
  color: var(--app-text);
  border-radius: 999px;
  padding: .7rem .95rem;
  box-shadow: 0 8px 20px rgba(15, 23, 42, .06);
}

.metric-card,
.stat-card,
.summary-card,
.kpi-card {
  border: 1px solid var(--app-border);
  border-radius: var(--card-radius);
  background: var(--app-surface);
  box-shadow: var(--app-shadow-sm);
}

/* El color del estado vive SOLO en el chip de icono (sin franjas laterales
   ni superiores: quedan prohibidas en todo el sistema). */
.metric-card {
  padding: 1.1rem;
  height: 100%;
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  overflow: hidden;
  position: relative;
}

.metric-card:hover {
  border-color: color-mix(in srgb, var(--metric-color, var(--app-primary)) 28%, var(--app-border));
}

.metric-label {
  color: var(--app-muted);
  font-size: .72rem;
  text-transform: uppercase;
  font-weight: 800;
  letter-spacing: .04em;
}

.metric-value {
  color: var(--app-text);
  font-size: clamp(1.35rem, 2.4vw, 2rem);
  line-height: 1.05;
  font-weight: 850;
  margin-top: .35rem;
}

.metric-note {
  color: var(--app-muted);
  font-size: .78rem;
  margin-top: .35rem;
}

.metric-icon {
  width: 44px;
  height: 44px;
  border-radius: 12px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: color-mix(in srgb, var(--metric-color, var(--app-primary)) 11%, white);
  color: var(--metric-color, var(--app-primary));
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--metric-color, var(--app-primary)) 18%, transparent);
  flex: 0 0 auto;
  font-size: 1.2rem;
}

.metric-danger { --metric-color: var(--app-danger); }
.metric-success { --metric-color: var(--app-success); }
.metric-warning { --metric-color: var(--app-warning); }
.metric-info { --metric-color: var(--app-info); }
.metric-primary { --metric-color: var(--app-primary); }
.metric-dark { --metric-color: #0f172a; }

/* === Métrica destacada: KPI card con marca de agua (dashboard, por cobrar) === */
.metric {
  --m: var(--app-primary);
  position: relative;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  gap: .25rem;
  min-height: 138px;
  padding: 1.05rem 1.15rem 1rem;
  background: var(--app-surface);
  border: 1px solid var(--app-border);
  border-radius: 16px;
  text-decoration: none;
  color: inherit;
  box-shadow: 0 1px 2px rgba(15, 23, 42, .04);
  transition: transform .18s ease, box-shadow .18s ease, border-color .18s ease;
}
.metric:hover {
  transform: translateY(-3px);
  box-shadow: 0 16px 32px rgba(15, 23, 42, .11);
  border-color: color-mix(in srgb, var(--m) 35%, var(--app-border));
  color: inherit;
}
.metric:focus-visible {
  outline: 3px solid color-mix(in srgb, var(--m) 35%, transparent);
  outline-offset: 2px;
}
.metric-wm {
  position: absolute;
  right: -8px;
  bottom: -18px;
  font-size: 5.6rem;
  line-height: 1;
  color: var(--m);
  opacity: .08;
  transform: rotate(-8deg);
  pointer-events: none;
}
.metric-eyebrow {
  display: flex;
  align-items: center;
  gap: .4rem;
  font-size: .7rem;
  font-weight: 800;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--app-muted);
}
.metric-eyebrow .dotmark {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--m);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--m) 18%, transparent);
}
.metric .metric-value {
  font-family: "SF Mono", "Roboto Mono", Menlo, monospace;
  font-size: clamp(1.5rem, 2.2vw, 1.9rem);
  font-weight: 800;
  letter-spacing: -.02em;
  line-height: 1.05;
  color: var(--app-text);
  margin-top: .15rem;
}
.metric-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: .5rem;
  margin-top: auto;
  padding-top: .55rem;
}
.metric-chip {
  font-size: .72rem;
  font-weight: 600;
  color: var(--app-muted);
}
.metric-go {
  font-size: .72rem;
  font-weight: 800;
  color: var(--m);
  opacity: 0;
  transform: translateX(-5px);
  transition: opacity .18s ease, transform .18s ease;
  white-space: nowrap;
}
.metric:hover .metric-go {
  opacity: 1;
  transform: none;
}
.metric.metric-alert {
  border-color: #fdba74;
  background: linear-gradient(180deg, #fffbf5, #fff);
}
@media (hover: none) {
  .metric-go {
    opacity: 1;
    transform: none;
  }
}
@media (max-width: 575.98px) {
  .metric {
    min-height: 120px;
    padding: .85rem .9rem;
  }
  .metric-wm {
    font-size: 4.4rem;
  }
}

.section-card {
  border: 1px solid var(--app-border);
  border-radius: var(--card-radius);
  background: var(--app-surface);
  box-shadow: var(--app-shadow-sm);
  overflow: hidden;
}

.section-header {
  padding: 1rem 1.15rem;
  border-bottom: 1px solid var(--app-border-soft);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: .75rem;
}

.section-title {
  margin: 0;
  font-size: 1rem;
  font-weight: 850;
}

.section-eyebrow {
  color: var(--app-muted);
  font-size: .74rem;
  text-transform: uppercase;
  letter-spacing: .05em;
  font-weight: 800;
}

.table-responsive {
  border-radius: 0 0 var(--card-radius) var(--card-radius);
}

.table {
  --bs-table-hover-bg: #f8fafc;
}

.table thead th {
  background: var(--app-surface-soft);
  color: #334155;
  border-bottom: 1px solid var(--app-border);
  font-size: .74rem;
  text-transform: uppercase;
  letter-spacing: .035em;
  font-weight: 850;
  white-space: nowrap;
}

.table tbody td,
.table tbody th {
  border-color: var(--app-border-soft);
  color: #1e293b;
  vertical-align: middle;
}

.table tbody tr:last-child td,
.table tbody tr:last-child th {
  border-bottom-width: 0;
}

/* Fila de cierre de una tabla de importes (totales, saldo final, diferencia).
   Cierra el bloque con superficie suave y un filete firme arriba. Se aplica a
   td y th por igual: si no, la celda de etiqueta arrastra un filete a medias. */
/* box-shadow: none es necesario: Bootstrap 5.3 pinta el fondo de cada celda con
   un inset box-shadow gigante que taparia el background-color. */
.table tbody tr.table-total-row > * {
  --bs-table-bg: var(--app-surface-soft);
  --bs-table-accent-bg: var(--app-surface-soft);
  background-color: var(--app-surface-soft);
  box-shadow: none;
  border-top: 1px solid var(--app-border);
  border-bottom-width: 0;
  font-weight: 850;
}

.empty-state {
  min-height: 180px;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-direction: column;
  gap: .55rem;
  color: var(--app-muted);
  text-align: center;
  padding: 2rem;
}

.empty-state i {
  font-size: 2.2rem;
  color: var(--app-success);
}

.quick-action {
  display: flex;
  align-items: center;
  gap: .85rem;
  height: 100%;
  padding: 1rem;
  border: 1px solid var(--app-border);
  border-radius: 14px;
  background: #fff;
  color: var(--app-text);
  text-decoration: none;
  box-shadow: var(--app-shadow-sm);
  transition: transform .18s ease, box-shadow .18s ease, border-color .18s ease;
}

.quick-action:hover {
  transform: translateY(-2px);
  border-color: rgba(37, 99, 235, .35);
  box-shadow: var(--app-shadow);
  color: var(--app-text);
}

.quick-action-icon {
  width: 42px;
  height: 42px;
  border-radius: 12px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: #eff6ff;
  color: var(--app-primary);
  flex: 0 0 auto;
  font-size: 1.1rem;
}

.quick-action-title {
  display: block;
  font-weight: 850;
  line-height: 1.15;
}

.quick-action-text {
  display: block;
  color: var(--app-muted);
  font-size: .78rem;
  margin-top: .18rem;
}

@media (max-width: 991.98px) {
  .app-page {
    padding-top: 1rem;
  }

  .app-hero {
    border-radius: 14px;
    padding: 1rem;
  }

  .section-header {
    align-items: flex-start;
    flex-direction: column;
  }
}

@media (max-width: 767.98px) {
  .container,
  .container-fluid,
  .container-lg,
  .container-md,
  .container-sm,
  .container-xl,
  .container-xxl {
    padding-left: .85rem;
    padding-right: .85rem;
  }

  .metric-card {
    padding: .95rem;
  }

  .metric-icon {
    width: 38px;
    height: 38px;
    border-radius: 11px;
  }


  .quick-action {
    padding: .9rem;
  }

  .quick-action-icon {
    width: 38px;
    height: 38px;
  }
}

/* ==========================================================================
   Tablas como tarjetas en pantallas estrechas
   --------------------------------------------------------------------------
   Corta a 991.98px (no a 768) a proposito: el navbar colapsa a 1199.98px, asi
   que entre 768 y 1199 el usuario ya esta en "modo movil" mental pero recibia
   tablas anchas. `enhanceMobileTables()` en app.js marca el contenedor y
   rellena `data-label` desde el thead; aqui solo se dibuja.

   Anatomia de la tarjeta:
     - `td.is-card-title`   -> titulo de la tarjeta (primera celda con texto)
     - `td` normal          -> par etiqueta/valor
     - `td.is-card-actions` -> pie con los botones, a ancho completo
     - `tfoot tr`           -> tarjeta de totales, acentuada
   ========================================================================== */
@media (max-width: 991.98px) {
  .mobile-card-table {
    overflow: visible !important;
    max-height: none !important;
    height: auto !important;
    min-height: 0 !important;
  }

  .mobile-card-table > table,
  .mobile-card-table > table thead,
  .mobile-card-table > table tbody,
  .mobile-card-table > table tfoot,
  .mobile-card-table > table tr,
  .mobile-card-table > table td {
    display: block;
    width: 100% !important;
    max-width: 100% !important;
  }

  .mobile-card-table > table thead {
    display: none;
  }

  .mobile-card-table > table tbody,
  .mobile-card-table > table tfoot {
    display: grid;
    gap: .85rem;
    padding: .85rem;
    background: var(--app-surface-soft);
  }

  /* El tfoot va pegado debajo del tbody: sin esto quedaria un hueco doble. */
  .mobile-card-table > table tbody + tfoot {
    padding-top: 0;
  }

  .mobile-card-table > table tbody tr,
  .mobile-card-table > table tfoot tr {
    background: #fff;
    border: 1px solid var(--app-border);
    border-radius: 13px;
    box-shadow: 0 3px 12px rgba(15, 23, 42, .05);
    overflow: hidden;
  }

  .mobile-card-table > table tbody td,
  .mobile-card-table > table tfoot td {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    /* Sin esto, un badge o un boton sueltos se estiran de borde a borde y la
       tarjeta parece un formulario. Cada valor ocupa lo que mide. */
    justify-items: start;
    gap: .35rem;
    padding: .75rem .85rem !important;
    border-bottom: 1px solid var(--app-border-soft);
    text-align: left !important;
    white-space: normal !important;
    min-width: 0;
    max-width: 100% !important;
    overflow-wrap: anywhere;
  }

  /* Excepcion a `justify-items: start`: los campos de formulario dentro de una
     tabla editable si tienen que ocupar el ancho de la tarjeta. */
  .mobile-card-table > table tbody td > .form-control,
  .mobile-card-table > table tbody td > .form-select,
  .mobile-card-table > table tbody td > .input-group,
  .mobile-card-table > table tbody td > textarea,
  .mobile-card-table > table tbody td > .progress {
    justify-self: stretch;
    width: 100%;
  }

  .mobile-card-table > table tbody td[style],
  .mobile-card-table > table tfoot td[style] {
    width: 100% !important;
    min-width: 0 !important;
    max-width: 100% !important;
  }

  .mobile-card-table > table tbody td::before,
  .mobile-card-table > table tfoot td::before {
    content: attr(data-label);
    color: var(--app-muted);
    font-size: .72rem;
    text-transform: uppercase;
    letter-spacing: .035em;
    font-weight: 850;
    text-align: left;
    line-height: 1.2;
  }

  .mobile-card-table > table tbody td:last-child,
  .mobile-card-table > table tfoot td:last-child {
    border-bottom: 0;
  }

  /* Celdas de relleno (colspan de cierre o celdas vacias): fuera de la tarjeta. */
  .mobile-card-table > table tbody td.is-card-filler,
  .mobile-card-table > table tfoot td.is-card-filler {
    display: none !important;
  }

  .mobile-card-table > table tbody td[colspan] {
    display: block;
    text-align: center !important;
  }

  .mobile-card-table > table tbody td[colspan]::before,
  .mobile-card-table > table tbody td[data-label=""]::before,
  .mobile-card-table > table tfoot td[data-label=""]::before {
    display: none;
  }

  /* --- Titulo de la tarjeta -------------------------------------------------
     La primera celda con contenido deja de ser un par etiqueta/valor y pasa a
     encabezar la tarjeta: da algo que leer de un vistazo al escanear la lista. */
  .mobile-card-table > table tbody td.is-card-title {
    background: var(--app-surface-soft);
    padding: .7rem .85rem !important;
    font-weight: 800;
    font-size: 1rem;
    /* Gana a `.text-muted`/`.small` de la celda: como titulo tiene que leerse
       antes que nada, aunque en la tabla ancha fuera un dato secundario. */
    color: var(--app-text) !important;
    border-bottom: 1px solid var(--app-border);
  }

  .mobile-card-table > table tbody td.is-card-title::before {
    font-size: .66rem;
    opacity: .85;
  }

  /* --- Pie de acciones ------------------------------------------------------
     Los botones se agrupan al final de la tarjeta con altura de toque real.
     `inline-flex` en vez de `flex` a proposito: cuando una tabla trae varias
     columnas de accion seguidas (p. ej. "Pagos" + "Eliminar"), asi caen en la
     misma linea sin tener que mover celdas de sitio en el DOM. */
  .mobile-card-table > table tbody td.is-card-actions {
    display: inline-flex !important;
    flex-wrap: wrap;
    align-items: center;
    gap: .45rem;
    width: auto !important;
    vertical-align: middle;
    padding: .55rem .35rem !important;
    border-bottom: 0;
  }

  .mobile-card-table > table tbody td.is-card-actions:first-of-type {
    padding-left: .8rem !important;
  }

  .mobile-card-table > table tbody td.is-card-actions::before {
    display: none;
  }

  .mobile-card-table > table tbody td.is-card-actions .btn {
    min-height: 44px;
    min-width: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: .35rem;
  }

  .mobile-card-table > table tbody td.is-card-actions .btn-group,
  .mobile-card-table > table tbody td.is-card-actions form {
    display: inline-flex;
    margin: 0;
  }

  /* Separador antes del pie: lo dibuja la ultima celda de datos, que si ocupa
     todo el ancho (las de accion van en linea y no llegan a los bordes). */
  .mobile-card-table > table tbody td:has(+ td.is-card-actions),
  .mobile-card-table > table tbody td:has(+ td.is-card-filler + td.is-card-actions) {
    border-bottom-color: var(--app-border);
  }

  /* --- Tarjeta de totales ---------------------------------------------------
     El tfoot es un resumen, no una fila mas. Antes se quedaba como tabla de N
     columnas dentro de un contenedor con overflow visible y se salia de la
     pantalla (los "TOTALES DEL PERIODO" cortados). */
  .mobile-card-table > table tfoot tr {
    /* Sin franja lateral de acento (ver DESIGN.md): el borde completo tenido y
       la sombra ya separan el resumen de las filas de datos. */
    border-color: color-mix(in srgb, var(--app-primary) 30%, var(--app-border));
    box-shadow: 0 4px 16px rgba(37, 99, 235, .08);
  }

  .mobile-card-table > table tfoot td {
    background: transparent !important;
    font-size: 1.02rem;
    font-weight: 800;
    padding: .7rem .85rem !important;
  }

  .mobile-card-table > table tfoot td[colspan] {
    display: block;
    background: var(--app-surface-soft) !important;
    font-size: .74rem;
    font-weight: 850;
    text-transform: uppercase;
    letter-spacing: .05em;
    color: var(--app-muted);
    border-bottom: 1px solid var(--app-border);
  }

  .mobile-card-table > table tfoot td[colspan]::before {
    display: none;
  }

  /* Bootstrap tinta el tfoot con .bg-light/.table-light; dentro de la tarjeta
     ese gris compite con el acento. */
  .mobile-card-table > table tfoot.bg-light,
  .mobile-card-table > table tfoot.table-light,
  .mobile-card-table > table tfoot.border-2 {
    border: 0 !important;
    background: var(--app-surface-soft) !important;
  }

  .mobile-card-table > table .btn,
  .mobile-card-table > table .btn-group,
  .mobile-card-table > table form {
    white-space: normal;
  }

  .mobile-card-table > table .btn-group,
  .mobile-card-table > table form {
    max-width: 100%;
  }

  .mobile-card-table > table .text-end,
  .mobile-card-table > table .text-center,
  .mobile-card-table > table [class*="text-end"],
  .mobile-card-table > table [class*="text-center"] {
    text-align: left !important;
  }

  .mobile-card-table > table .dual-currency,
  .mobile-card-table > table .receipt-actions,
  .mobile-card-table > table .mail-actions,
  .mobile-card-table > table .btn-group {
    align-items: flex-start !important;
    justify-content: flex-start !important;
  }

  .mobile-card-table > table .receipt-actions,
  .mobile-card-table > table .mail-actions {
    display: flex !important;
    flex-wrap: wrap;
    width: 100%;
    min-width: 0 !important;
    white-space: normal !important;
  }

  .mobile-card-table > table .text-truncate {
    max-width: 100% !important;
    white-space: normal !important;
    overflow: visible !important;
    text-overflow: clip !important;
  }
}

/* ==========================================================================
   Confort tactil general (solo pantallas estrechas)
   ========================================================================== */
@media (max-width: 991.98px) {
  /* Nada de scroll horizontal de pagina: si una vista se pasa de ancho, se
     recorta en vez de romper el layout. `clip` no crea contexto de scroll, asi
     que los sticky y los dropdown siguen funcionando. */
  html,
  body {
    overflow-x: clip;
  }

  /* iOS hace zoom al enfocar un input de menos de 16px. Los .form-control-sm
     de las barras de filtro caian justo debajo. */
  .form-control,
  .form-select,
  .form-control-sm,
  .form-select-sm,
  textarea.form-control {
    font-size: 16px;
  }

  .form-control-sm,
  .form-select-sm {
    min-height: 42px;
    padding-top: .45rem;
    padding-bottom: .45rem;
  }

  /* Area de toque minima en acciones sueltas (no en las de dentro de tabla,
     que ya la fijan arriba). */
  .btn:not(.btn-close):not(.btn-link) {
    min-height: 42px;
  }

  .btn-sm {
    min-height: 38px;
  }

  /* PWA instalada: la barra de gestos de Android/iOS se comia el ultimo boton.
     Va en el body para cubrir todas las vistas, usen `.app-page` o no. */
  body {
    padding-bottom: env(safe-area-inset-bottom, 0px);
  }

  .app-page {
    padding-bottom: 2.5rem;
  }
}

/* ==========================================================================
   Panel de filtros plegable
   --------------------------------------------------------------------------
   Patron `<details class="card mobile-filters" open>`: el atributo `open` va
   siempre en el HTML, asi que en escritorio se ve exactamente igual que antes
   (una card con su header y su cuerpo; el `summary` esta oculto). En movil,
   `setupMobileFilters()` en app.js lo cierra al cargar: los filtros dejan de
   comerse la primera pantalla antes de llegar a los datos.
   ========================================================================== */
.mobile-filters > summary {
  display: none;
}

@media (max-width: 991.98px) {
  /* El summary sustituye a la cabecera: mismo sitio, ahora pulsable. */
  .mobile-filters > .card-header,
  .mobile-filters > .ops-card-head {
    display: none;
  }

  /* Cuando el desplegable no es una card ya dibujada, se dibuja el mismo. */
  .mobile-filters.is-bare {
    border: 1px solid var(--app-border);
    border-radius: var(--card-radius);
    background: var(--app-surface);
    overflow: hidden;
  }

  .mobile-filters.is-bare[open] > summary + * {
    padding: .85rem;
  }

  .mobile-filters > summary {
    display: flex;
    align-items: center;
    gap: .5rem;
    cursor: pointer;
    list-style: none;
    padding: .75rem 1rem;
    background: var(--app-surface-soft);
    border-bottom: 1px solid var(--app-border-soft);
    border-radius: var(--card-radius) var(--card-radius) 0 0;
    font-weight: 800;
    font-size: .82rem;
    text-transform: uppercase;
    letter-spacing: .04em;
    color: var(--app-text);
    min-height: 46px;
    -webkit-tap-highlight-color: transparent;
  }

  .mobile-filters > summary::-webkit-details-marker {
    display: none;
  }

  .mobile-filters > summary::after {
    content: "\F282"; /* bi-chevron-down */
    font-family: "bootstrap-icons";
    margin-left: auto;
    font-size: .85rem;
    font-weight: 400;
    color: var(--app-muted);
    transition: transform .18s ease;
  }

  .mobile-filters[open] > summary::after {
    transform: rotate(180deg);
  }

  .mobile-filters:not([open]) > summary {
    border-bottom-color: transparent;
    border-radius: var(--card-radius);
  }

  /* Resumen de lo que hay filtrado, para no tener que abrir para saberlo. */
  .mobile-filters-hint {
    font-weight: 600;
    font-size: .74rem;
    text-transform: none;
    letter-spacing: 0;
    color: var(--app-muted);
  }
}

@media (prefers-reduced-motion: reduce) {
  .mobile-filters > summary::after {
    transition: none;
  }
}

/* ==========================================================================
   Modales en movil: hoja, no pantalla completa
   --------------------------------------------------------------------------
   A pantalla completa el modal se confunde con una pagina nueva y se pierde el
   sitio del que venias. Como hoja anclada abajo se sigue viendo la pantalla
   detras (desenfocada), que es lo que dice "esto es un paso, no un viaje".
   Nunca pasa del 92% del alto: si el contenido es largo, scrollea el cuerpo.
   ========================================================================== */
@media (max-width: 767.98px) {
  /* El velo se difumina en vez de solo oscurecer. */
  .modal-backdrop.show {
    opacity: 1;
    background: rgba(15, 23, 42, .45);
  }

  @supports ((backdrop-filter: blur(6px)) or (-webkit-backdrop-filter: blur(6px))) {
    .modal-backdrop.show {
      background: rgba(15, 23, 42, .32);
      -webkit-backdrop-filter: blur(6px);
      backdrop-filter: blur(6px);
    }
  }

  .modal-dialog:not(.modal-keep-centered):not(.modal-sm) {
    width: 100%;
    max-width: none;
    margin: 0;
    /* Ancla abajo: el pulgar llega antes al pie que a la cabecera. */
    position: absolute;
    bottom: 0;
    left: 0;
    right: 0;
    align-items: flex-end;
    min-height: 0;
  }

  .modal-dialog:not(.modal-keep-centered):not(.modal-sm) .modal-content {
    max-height: 92vh;
    max-height: 92dvh;
    border: 0;
    border-radius: 18px 18px 0 0;
    box-shadow: 0 -12px 40px rgba(15, 23, 42, .22);
  }

  /* Asa de arrastre: dice "esto se cierra tirando hacia abajo" sin texto, y el
     gesto lo implementa setupSheetDrag() en app.js.

     Va DENTRO de la cabecera, no encima. Colgada del lienzo blanco quedaba
     flotando en una franja por encima de las cabeceras de color (el modal de
     "Nuevo cobro extra" tiene la suya en azul noche) y la hoja parecia partida
     en dos. `currentColor` la resuelve sola: gris sobre cabecera clara, blanca
     sobre cabecera oscura. */
  .modal-dialog:not(.modal-keep-centered):not(.modal-sm) .modal-header::before {
    content: "";
    position: absolute;
    top: .45rem;
    left: 50%;
    transform: translateX(-50%);
    width: 2.4rem;
    height: .25rem;
    border-radius: 999px;
    background: currentColor;
    opacity: .28;
  }

  /* Los pocos modales sin cabecera propia la siguen llevando en el lienzo. */
  .modal-dialog:not(.modal-keep-centered):not(.modal-sm) .modal-content:not(:has(> .modal-header))::before {
    content: "";
    display: block;
    width: 2.4rem;
    height: .25rem;
    margin: .55rem auto .1rem;
    border-radius: 999px;
    background: var(--app-border);
    flex: 0 0 auto;
  }

  .modal-dialog:not(.modal-keep-centered):not(.modal-sm) .modal-body {
    overflow-y: auto;
    overscroll-behavior: contain;
  }

  .modal-dialog:not(.modal-keep-centered):not(.modal-sm) .modal-header {
    /* El asa se posiciona contra esta cabecera, y el relleno de arriba le hace
       sitio para que no choque con el titulo. */
    position: relative;
    padding-top: 1.05rem;
    padding-bottom: .65rem;
  }

  /* Pie anclado: los botones de confirmar quedan siempre al alcance, aunque el
     cuerpo del modal sea largo. */
  .modal-dialog:not(.modal-keep-centered):not(.modal-sm) .modal-footer {
    background: var(--app-surface);
    border-top: 1px solid var(--app-border);
    padding-bottom: calc(.75rem + env(safe-area-inset-bottom, 0px));
    gap: .5rem;
    flex: 0 0 auto;
  }

  .modal-dialog:not(.modal-keep-centered):not(.modal-sm) .modal-footer > .btn {
    flex: 1 1 auto;
    min-height: 46px;
    margin: 0;
  }

  /* Los pequenos de confirmacion si se quedan centrados, flotando. */
  .modal-dialog.modal-sm {
    width: calc(100% - 2.5rem);
    margin: auto;
  }

  /* La hoja entra desde abajo, no cayendo desde arriba como el modal normal. */
  .modal.fade .modal-dialog:not(.modal-keep-centered):not(.modal-sm) {
    transform: translateY(100%);
  }

  .modal.show .modal-dialog:not(.modal-keep-centered):not(.modal-sm) {
    transform: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  .modal.fade .modal-dialog { transition: none; }
}

/* ==========================================================================
   Avisos de accion completada
   --------------------------------------------------------------------------
   Un "guardado correctamente" es un acuse de recibo, no una noticia: se
   confirma de reojo y desaparece. Por eso es una pastilla y no una tarjeta, no
   lleva icono dibujado ni barra de progreso, y no bloquea nada. Los avisos que
   si hay que leer (`.alert-info`, `.alert-warning`) siguen inline en la pagina.

   En escritorio cae arriba a la derecha; en movil sube desde abajo, porque
   arriba tapaba el titulo de la pantalla justo donde vuelve la vista despues
   de guardar.
   ========================================================================== */
.app-toasts {
  position: fixed;
  z-index: 1090; /* sobre el contenido, bajo modales (1055) no: los pisa a proposito */
  top: 1rem;
  right: 1rem;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: .5rem;
  pointer-events: none;
}

.app-toast {
  display: flex;
  align-items: center;
  gap: .55rem;
  max-width: min(24rem, calc(100vw - 2rem));
  padding: .6rem .95rem;
  border: 1px solid var(--app-border);
  border-radius: 999px;
  background: var(--app-surface);
  color: var(--app-text);
  font-size: .88rem;
  font-weight: 600;
  line-height: 1.35;
  box-shadow: 0 8px 24px rgba(15, 23, 42, .14);
  pointer-events: auto;
  cursor: default;
  animation: appToastIn .16s ease-out;
}

/* Un punto de color en vez de un icono: dice exito o fallo sin pedir atencion. */
.app-toast-dot {
  width: .5rem;
  height: .5rem;
  border-radius: 999px;
  flex: 0 0 auto;
  background: var(--app-success);
}

.app-toast-error {
  border-color: color-mix(in srgb, var(--app-danger) 35%, var(--app-border));
}

.app-toast-error .app-toast-dot {
  background: var(--app-danger);
}

.app-toast.is-leaving {
  animation: appToastOut .14s ease-in forwards;
}

@keyframes appToastIn {
  from { opacity: 0; transform: translateY(-6px); }
  to { opacity: 1; transform: none; }
}

@keyframes appToastOut {
  from { opacity: 1; transform: none; }
  to { opacity: 0; transform: translateY(-6px); }
}

@media (max-width: 767.98px) {
  /* --tabbar-h lo define partials/tabbar.php y solo existe cuando la barra
     inferior esta en pantalla; sin ella vale 0 y el aviso queda donde estaba. */
  .app-toasts {
    top: auto;
    right: .75rem;
    left: .75rem;
    bottom: calc(1rem + var(--tabbar-h, 0px) + env(safe-area-inset-bottom, 0px));
    align-items: center;
  }

  .app-toast {
    animation-name: appToastInMovil;
  }

  .app-toast.is-leaving {
    animation-name: appToastOutMovil;
  }

  @keyframes appToastInMovil {
    from { opacity: 0; transform: translateY(10px); }
    to { opacity: 1; transform: none; }
  }

  @keyframes appToastOutMovil {
    from { opacity: 1; transform: none; }
    to { opacity: 0; transform: translateY(10px); }
  }
}

@media (prefers-reduced-motion: reduce) {
  .app-toast,
  .app-toast.is-leaving {
    animation: none;
  }
}


/* ==========================================================================
   NAVBAR (escritorio y tabletas)
   Vivia incrustado en navbar.php. Se movio aqui porque era
   identico en todas las paginas y al ir inline el navegador no podia
   reutilizarlo: reconstruia el CSSOM entero en cada navegacion.
   ========================================================================== */
    :root {
        --nav-bg: #0f172a;
        --nav-bg-soft: #111827;
        --nav-border: rgba(255,255,255,0.06);
        --nav-text: #cbd5e1;
        --nav-muted: #94a3b8;
        --nav-white: #ffffff;
        --nav-hover: rgba(255,255,255,0.05);
        --nav-active: rgba(56,189,248,0.10);
        --nav-accent: #38bdf8;
        --nav-dropdown: #111827;
    }

    .navbar-custom {
        background-color: var(--nav-bg);
        border-bottom: 1px solid var(--nav-border);
        padding-top: 0.55rem;
        padding-bottom: 0.55rem;
        z-index: 1050;
        /* El padding NO se anima: la barra es `sticky-top`, o sea que sigue
           ocupando su hueco en el flujo. Encogerla al scrollear cambiaba la
           altura del hueco, el contenido de abajo saltaba, y cerca del umbral
           ese salto volvia a cruzar el umbral: la barra se quedaba temblando
           al subir y bajar. Ahora solo cambian pintadas (sombra, borde, fondo),
           que no reflowean nada. */
        transition: box-shadow .24s ease,
                    background-color .24s ease,
                    border-color .24s ease;
    }

    /* Al bajar, la barra despega del contenido. Arriba del todo queda plana:
       la sombra solo aparece cuando hay algo debajo que cubrir. */
    .navbar-custom.is-scrolled {
        border-bottom-color: rgba(255,255,255,0.10);
        box-shadow: 0 10px 30px rgba(2, 6, 23, .35);
    }
    /* El desenfoque solo en escritorio. En una barra `sticky` obliga al
       navegador a recomponer la pantalla entera en cada frame de scroll: en
       Android de gama media eso es el tiron que se siente al bajar. Arriba de
       1200px la barra ya no es sticky sobre contenido que se mueve tan rapido
       y la GPU tiene margen. */
    @media (min-width: 1200px) {
        @supports ((backdrop-filter: blur(12px)) or (-webkit-backdrop-filter: blur(12px))) {
            .navbar-custom.is-scrolled {
                background-color: rgba(15, 23, 42, .86);
                -webkit-backdrop-filter: blur(14px) saturate(150%);
                backdrop-filter: blur(14px) saturate(150%);
            }
        }
    }

    /* En movil la sombra es mas corta: la larga de escritorio, sobre un fondo
       que se mueve rapido bajo el dedo, se lee como una mancha sucia. */
    @media (max-width: 1199.98px) {
        .navbar-custom {
            transition: box-shadow .2s ease, border-color .2s ease;
        }

        .navbar-custom.is-scrolled {
            box-shadow: 0 6px 18px rgba(2, 6, 23, .30);
        }
    }

    .navbar-brand {
        color: var(--nav-white) !important;
        font-weight: 700;
        font-size: 1.05rem;
        letter-spacing: -0.3px;
    }

    .brand-icon {
        width: 32px;
        height: 32px;
        border-radius: 10px;
        display: inline-flex;
        align-items: center;
        justify-content: center;
        background: rgba(56, 189, 248, 0.12);
        color: var(--nav-accent);
        font-size: 0.95rem;
    }

    /* === Riel de navegacion ===
       El resaltado no vive en cada item: es UN indicador que se desliza entre
       ellos (ver JS al final). Por eso los .nav-link no llevan fondo propio; si
       lo llevaran, se verian dos resaltados a la vez durante la animacion. */
    .navbar-custom .navbar-nav {
        position: relative;
    }

    .nav-indicator {
        position: absolute;
        top: 0;
        left: 0;
        z-index: 0;
        width: 0;
        height: 0;
        border-radius: 10px;
        background: rgba(255, 255, 255, .07);
        box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .05);
        opacity: 0;
        pointer-events: none;
        transition: transform .34s cubic-bezier(.22, 1, .36, 1),
                    width .34s cubic-bezier(.22, 1, .36, 1),
                    height .34s cubic-bezier(.22, 1, .36, 1),
                    background-color .18s ease,
                    opacity .18s ease;
    }
    .nav-indicator.is-ready { opacity: 1; }
    /* La capa de composicion solo mientras el indicador se mueve de verdad. El
       riel no existe por debajo de 1200px, asi que ahi nunca se reserva. */
    @media (min-width: 1200px) {
        .nav-indicator.is-hovering,
        .nav-indicator.is-moving { will-change: transform, width; }
    }
    /* Al perseguir el cursor se aclara un punto: distingue "estoy pasando por
       aqui" de "esta es la seccion en la que estoy". */
    .nav-indicator.is-hovering { background: rgba(255, 255, 255, .11); }

    .navbar-custom .nav-link {
        position: relative;   /* por encima del indicador */
        z-index: 1;
        color: var(--nav-text) !important;
        font-size: 0.94rem;
        font-weight: 500;
        padding: 0.6rem 0.85rem !important;
        border-radius: 10px;
        transition: color .2s ease;
    }

    .navbar-custom .nav-link i {
        margin-right: 6px;
        opacity: 0.85;
        transition: color .2s ease, opacity .2s ease;
    }

    .navbar-custom .nav-link:hover,
    .navbar-custom .show > .nav-link {
        color: var(--nav-white) !important;
    }

    /* Seccion activa: sin negrita a proposito. Cambiar el peso ensancha el item
       y el indicador daria un salto al llegar. El color hace el trabajo. */
    .navbar-custom .nav-link.active {
        color: var(--nav-white) !important;
    }

    .navbar-custom .nav-link.active i {
        opacity: 1;
        color: var(--nav-accent) !important;
    }

    /* Foco: sin recuadro brusco al hacer clic; con teclado, halo suave. */
    .navbar-custom .nav-link:focus { outline: none; }
    .navbar-custom .nav-link:focus-visible {
        outline: none;
        box-shadow: 0 0 0 2px var(--nav-bg), 0 0 0 4px rgba(56, 189, 248, .55);
    }

    /* Menu colapsado (<1200px): el riel se apila en vertical y el indicador se
       apaga, asi que ahi el resaltado clasico por item vuelve a hacer falta. */
    @media (max-width: 1199.98px) {
        .nav-indicator { display: none; }
        .navbar-custom .nav-link:hover,
        .navbar-custom .show > .nav-link { background-color: var(--nav-hover); }
        .navbar-custom .nav-link.active { background-color: var(--nav-active); }
    }

    /* Entrada suave de menus */
    @keyframes navMenuIn {
        from { opacity: 0; transform: translateY(-6px); }
        to   { opacity: 1; transform: translateY(0); }
    }

    .navbar-custom .dropdown-menu.show {
        animation: navMenuIn .16s ease-out;
    }

    @media (prefers-reduced-motion: reduce) {
        .navbar-custom .dropdown-menu.show { animation: none; }
        /* El indicador sigue marcando donde estas: aparece en destino, no viaja. */
        .nav-indicator { transition: opacity .12s ease; }
        .navbar-custom { transition: none; }
    }

    .navbar-custom .dropdown-menu {
        background-color: var(--nav-dropdown) !important;
        border: 1px solid var(--nav-border) !important;
        border-radius: 12px !important;
        padding: 0.45rem !important;
        box-shadow: 0 10px 25px rgba(0,0,0,0.18) !important;
    }

    .navbar-custom .dropdown-item {
        color: var(--nav-text) !important;
        border-radius: 8px;
        padding: 0.65rem 0.8rem;
        font-size: 0.92rem;
        transition: background-color .2s ease, color .2s ease;
    }

    .navbar-custom .dropdown-item:hover,
    .navbar-custom .dropdown-item:focus {
        background-color: var(--nav-hover) !important;
        color: var(--nav-white) !important;
    }

    .navbar-custom .dropdown-header {
        color: var(--nav-muted) !important;
        font-size: 0.78rem;
        font-weight: 600;
        padding: 0.55rem 0.8rem;
    }

    .navbar-custom .dropdown-divider {
        border-color: var(--nav-border) !important;
    }

    .nav-badge {
        min-width: 18px;
        height: 18px;
        border-radius: 999px;
        display: inline-flex;
        align-items: center;
        justify-content: center;
        padding: 0 5px;
        font-size: 0.68rem;
        font-weight: 800;
        background: #ef4444;
        color: #fff;
        margin-left: 6px;
    }

    .nav-presence-dot {
        width: 8px;
        height: 8px;
        border-radius: 999px;
        display: inline-block;
        background: #22c55e;
        box-shadow: 0 0 0 3px rgba(34, 197, 94, .14);
        margin-left: 6px;
        vertical-align: middle;
    }

    .navbar-custom .nav-item.dropdown,
    .navbar-custom .nav-item.dropdown.position-static {
        position: relative !important;
    }

    .mega-dropdown-menu {
        width: min(680px, calc(100vw - 32px));
        padding: 0 !important;
        overflow: hidden;
        left: 0 !important;
        right: auto !important;
        top: calc(100% + 8px) !important;
    }

    .mega-dropdown-menu > li:not(.mega-wrap) {
        display: none;
    }

    .mega-panel {
        display: grid;
        grid-template-columns: 220px 1fr 1fr;
        gap: 0;
    }

    .mega-intro {
        background: linear-gradient(145deg, rgba(56,189,248,.15), rgba(15,23,42,.2));
        padding: 1rem;
        border-right: 1px solid var(--nav-border);
    }

    .mega-intro-icon {
        width: 42px;
        height: 42px;
        border-radius: 12px;
        display: inline-flex;
        align-items: center;
        justify-content: center;
        background: rgba(56,189,248,.13);
        color: #7dd3fc;
        font-size: 1.15rem;
        margin-bottom: .75rem;
    }

    .mega-title {
        color: #fff;
        font-weight: 800;
        margin-bottom: .25rem;
    }

    .mega-text {
        color: var(--nav-muted);
        font-size: .8rem;
        margin: 0;
    }

    .mega-col {
        padding: .7rem;
    }

    .mega-item {
        display: flex;
        align-items: center;
        gap: .7rem;
        color: var(--nav-text);
        text-decoration: none;
        padding: .55rem;
        border-radius: 10px;
        transition: background-color .2s ease, color .2s ease;
    }

    .mega-item:hover {
        background: var(--nav-hover);
        color: #fff;
    }

    /* Item exacto = pagina actual: acento + punto indicador (no solo color) */
    .mega-item.is-current {
        background: var(--nav-active);
    }

    .mega-item.is-current .mega-item-icon {
        background: rgba(56, 189, 248, .18);
        color: var(--nav-accent);
    }

    .mega-item.is-current .mega-item-label {
        color: #fff;
    }

    .mega-item.is-current::after {
        content: "";
        flex: 0 0 auto;
        margin-left: auto;
        width: 7px;
        height: 7px;
        border-radius: 999px;
        background: var(--nav-accent);
        box-shadow: 0 0 0 3px rgba(56, 189, 248, .15);
    }

    .mega-item-icon {
        width: 32px;
        height: 32px;
        border-radius: 9px;
        display: inline-flex;
        align-items: center;
        justify-content: center;
        background: rgba(255,255,255,.06);
        color: #93c5fd;
        flex: 0 0 auto;
    }

    .mega-item-label {
        display: block;
        font-size: .9rem;
        font-weight: 700;
        line-height: 1.15;
    }

    .mega-item-help {
        display: block;
        color: var(--nav-muted);
        font-size: .74rem;
        line-height: 1.2;
        margin-top: 2px;
    }

    .global-search {
        position: relative;
        min-width: 280px;
        max-width: 320px;
    }

    /* Selector de edificio activo: mismo lenguaje visual que el buscador, para
       que se lea como parte de la barra y no como un filtro de la pantalla. */
    .building-switch {
        min-width: 210px;
        max-width: 260px;
    }

    .building-switch .input-group-text,
    .building-switch .form-select {
        background: rgba(255,255,255,.06);
        border-color: rgba(255,255,255,.08);
        color: #fff;
    }

    .building-switch .form-select {
        cursor: pointer;
    }

    .building-switch .form-select:focus {
        box-shadow: none;
        border-color: rgba(255,255,255,.25);
    }

    /* El desplegable nativo se pinta con los colores del sistema: sin esto las
       opciones salen en blanco sobre blanco en tema oscuro. */
    .building-switch .form-select option {
        background: #0f172a;
        color: #fff;
    }

    .global-search .input-group-text,
    .global-search .form-control {
        background: rgba(255,255,255,.06);
        border-color: rgba(255,255,255,.08);
        color: #fff;
    }

    .global-search .form-control::placeholder {
        color: #94a3b8;
    }

    .global-search .form-control:focus {
        box-shadow: 0 0 0 3px rgba(56,189,248,.16);
        border-color: rgba(125,211,252,.35);
    }

    .global-search-results {
        position: absolute;
        right: 0;
        top: calc(100% + 8px);
        width: min(460px, 92vw);
        background: #0f172a;
        border: 1px solid var(--nav-border);
        border-radius: 12px;
        box-shadow: 0 18px 34px rgba(0,0,0,.28);
        padding: .45rem;
        z-index: 1080;
        display: none;
    }

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

    .search-result-item {
        display: flex;
        gap: .7rem;
        padding: .7rem;
        border-radius: 10px;
        text-decoration: none;
        color: var(--nav-text);
    }

    .search-result-item:hover {
        background: var(--nav-hover);
        color: #fff;
    }

    .search-result-icon {
        width: 34px;
        height: 34px;
        border-radius: 9px;
        display: inline-flex;
        align-items: center;
        justify-content: center;
        background: rgba(56,189,248,.12);
        color: #7dd3fc;
        flex: 0 0 auto;
    }

    .search-result-title {
        display: block;
        font-size: .9rem;
        font-weight: 800;
    }

    .search-result-meta {
        display: block;
        color: var(--nav-muted);
        font-size: .76rem;
        margin-top: 1px;
    }

    .search-empty {
        color: var(--nav-muted);
        padding: .85rem;
        font-size: .86rem;
    }

    .user-trigger {
        padding: 0.35rem 0.45rem;
        border-radius: 12px;
        transition: background-color .2s ease;
    }

    .user-trigger:hover {
        background-color: rgba(255,255,255,0.04);
    }

    .user-avatar {
        width: 36px;
        height: 36px;
        background-color: rgba(56, 189, 248, 0.12);
        color: #7dd3fc;
        border-radius: 50%;
        display: flex;
        align-items: center;
        justify-content: center;
        font-weight: 700;
        font-size: 0.82rem;
        border: 1px solid rgba(255,255,255,0.08);
    }

    .user-name {
        font-size: 0.84rem;
        font-weight: 600;
        color: #fff;
    }

    .user-role {
        font-size: 0.72rem;
        color: var(--nav-muted);
        text-transform: capitalize;
    }

    .btn-login-custom {
        border-radius: 999px;
        padding: 0.55rem 1rem;
        font-weight: 600;
        background-color: var(--nav-accent);
        border: none;
        color: #082f49 !important;
    }

    .btn-login-custom:hover {
        filter: brightness(1.03);
        color: #082f49 !important;
    }

    .navbar-toggler {
        border: 1px solid var(--nav-border) !important;
        box-shadow: none !important;
        border-radius: 10px;
    }

    @media (max-width: 1399px) {
        .global-search {
            min-width: 230px;
            max-width: 260px;
        }

        .nav-link {
            padding-left: .65rem !important;
            padding-right: .65rem !important;
        }
    }

    /* === El riel no se parte ===
       Entre 1200 y ~1450px la barra lleva cinco secciones + selector de
       edificio + buscador + usuario, y no cabe todo. Al comprimirse, el item
       mas corto ("Dashboard", sin flecha de desplegable) partia el icono a una
       linea y el texto a otra: la barra crecia de alto y se veia rota.
       Ahora los rotulos no se parten nunca y el que cede es el buscador, que
       es lo unico que aguanta encogerse sin perder sentido. */
    .navbar-custom .nav-link {
        white-space: nowrap;
    }

    @media (min-width: 1200px) {
        .navbar-custom .navbar-nav > .nav-item {
            flex: 0 0 auto;
        }

        /* El `w-100` del bloque de usuario existe para el panel colapsado, donde
           tiene que ocupar toda la fila. En horizontal, en cambio, hacia que
           pidiera el ancho entero del contenedor y ahogara al buscador y al
           selector de edificio, que se quedaban a la mitad. */
        .user-menu {
            width: auto !important;
            flex: 0 0 auto;
        }

        /* `flex-basis` en vez de `min-width`: piden su ancho comodo y solo
           encogen si de verdad no cabe, en vez de quedarse encogidos siempre. */
        .global-search {
            flex: 0 1 280px;
            min-width: 0;
        }

        .building-switch {
            flex: 0 1 210px;
            min-width: 0;
        }

        /* Sin esto, el texto largo del edificio fija el ancho minimo del
           `select` y el encogido no llega a ninguna parte. */
        .building-switch .form-select,
        .global-search .form-control {
            min-width: 0;
        }
    }

    @media (max-width: 1199px) {
        .navbar-custom .nav-item.dropdown.position-static {
            position: static !important;
        }

        .navbar-collapse {
            background-color: var(--nav-bg-soft);
            margin-top: 10px;
            border-radius: 12px;
            padding: 10px;
            border: 1px solid var(--nav-border);
        }

        .navbar-nav .dropdown-menu {
            position: static;
            float: none;
            width: auto;
            margin-top: 6px;
            box-shadow: none !important;
            transform: none !important;
            /* Sin scroll propio: el que scrollea es el panel. Dos superficies de
               scroll anidadas en un telefono se pelean por el gesto. */
            max-height: none;
            overflow: visible;
        }

        .mega-panel {
            grid-template-columns: 1fr;
        }

        .mega-dropdown-menu {
            width: 100%;
            top: auto !important;
            border-radius: 10px !important;
        }

        .mega-intro {
            border-right: 0;
            border-bottom: 1px solid var(--nav-border);
            padding: .85rem;
        }

        .mega-intro-icon {
            width: 34px;
            height: 34px;
            margin-bottom: .45rem;
        }

        .mega-col {
            padding: .55rem;
        }

        .mega-item {
            padding: .65rem;
        }

        .global-search {
            min-width: 100%;
            max-width: 100%;
            width: 100%;
        }

        .global-search-results {
            left: 0;
            right: auto;
            width: 100%;
        }

        .user-divider-mobile {
            border-top: 1px solid var(--nav-border);
            margin-top: 10px;
            padding-top: 10px;
            flex-direction: column;
            align-items: stretch !important;
            gap: .75rem !important;
        }

        /* El menu de usuario cuelga de un `.dropdown` suelto, no de `.navbar-nav`,
           asi que la regla de arriba no lo alcanzaba: se posicionaba absoluto y
           se salia del panel, flotando encima de la pagina. Aqui se apila dentro
           del panel como un submenu mas. */
        .user-menu .dropdown-menu {
            position: static !important;
            float: none;
            width: 100%;
            margin-top: 8px !important;
            transform: none !important;
            inset: auto !important;
            box-shadow: none !important;
            max-height: 60vh;
            overflow-y: auto;
        }

        .user-menu .dropdown-item {
            padding-top: .6rem;
            padding-bottom: .6rem;
        }

        /* El panel entero scrollea: con el menu de usuario abierto en un telefono
           bajo, "Cerrar sesion" quedaba fuera de alcance. Solo en `.show` para no
           pelear con el `overflow: hidden` de la animacion de Bootstrap. */
        .navbar-collapse.show {
            max-height: calc(100vh - 5rem);
            max-height: calc(100dvh - 5rem);
            overflow-y: auto;
            overscroll-behavior: contain;
            -webkit-overflow-scrolling: touch;
        }
    }

    @media (max-width: 575px) {
        .navbar-custom .container-xl {
            padding-left: .75rem;
            padding-right: .75rem;
        }

        .navbar-brand {
            font-size: .98rem;
        }

        .brand-icon {
            width: 30px;
            height: 30px;
            border-radius: 8px;
        }

        .mega-text,
        .mega-item-help {
            display: none;
        }
    }

    /* =======================================================================
       Telefono: la barra superior no se muestra
       -----------------------------------------------------------------------
       Por debajo de 992px la navegacion entera vive en la barra inferior
       (partials/tabbar.php), y el edificio activo se elige dentro de su hoja
       de "Mas". Lo unico que quedaba aqui arriba era el nombre de la app -que
       el usuario ya sabe- y el chip de contexto: ~56px fijos robados a una
       pantalla de telefono en cada vista, mas una franja sticky que el
       navegador recomponia mientras el dedo arrastraba.
       No se borra del HTML a proposito: el panel colapsado sigue siendo el
       unico sitio donde vive el buscador con su id de siempre, y las tabletas
       (992-1199px) lo usan tal cual. De 992px en adelante nada cambia.
       ======================================================================= */
    .brand-name-movil,
    .nav-context-chip { display: none; }

    @media (max-width: 991.98px) {
        .navbar-custom { display: none !important; }
    }


/* ==========================================================================
   BARRA SUPERIOR DE MOVIL (campana / edificio / sesion)
   Vivia incrustado en topbar_movil.php. Se movio aqui porque era
   identico en todas las paginas y al ir inline el navegador no podia
   reutilizarlo: reconstruia el CSSOM entero en cada navegacion.
   ========================================================================== */
/* Igual que la barra inferior: nada de esto existe por encima de 991.98px. */
.topbar-movil { display: none; }

@media (max-width: 991.98px) {
    .topbar-movil {
        position: sticky;
        top: 0;
        z-index: 1030; /* sobre el contenido, debajo de las hojas (1040) */
        display: flex;
        align-items: center;
        gap: .5rem;
        /* El head declara `apple-mobile-web-app-status-bar-style:
           black-translucent`, o sea que en la PWA instalada la pagina sube
           HASTA el borde de la pantalla. Sin reservar el hueco, la campana y
           el avatar quedaban debajo del reloj y la señal. El alto sigue
           siendo 52px de contenido: el inset se suma por fuera. */
        height: calc(52px + env(safe-area-inset-top, 0px));
        padding: env(safe-area-inset-top, 0px) .6rem 0;
        background: #0f172a;
        border-bottom: 1px solid rgba(148, 163, 184, .18);
    }

    .topbar-btn {
        position: relative;
        flex: 0 0 auto;
        width: 40px;
        height: 40px;
        display: flex;
        align-items: center;
        justify-content: center;
        border: 0;
        border-radius: 999px;
        background: none;
        color: #cbd5e1;
        font-size: 1.25rem;
        -webkit-tap-highlight-color: transparent;
        transition: background .12s ease, color .12s ease;
    }

    .topbar-btn:active {
        background: rgba(148, 163, 184, .16);
        color: #e2e8f0;
    }

    /* El globo del contador. Se recorta a "99+" porque a partir de ahi el
       numero exacto ya no cambia ninguna decision. */
    .topbar-badge {
        position: absolute;
        top: 2px;
        right: 1px;
        min-width: 17px;
        height: 17px;
        padding: 0 4px;
        display: flex;
        align-items: center;
        justify-content: center;
        border-radius: 999px;
        border: 2px solid #0f172a;
        background: #ef4444;
        color: #fff;
        font-size: .6rem;
        font-weight: 700;
        line-height: 1;
        font-variant-numeric: tabular-nums;
    }

    /* Centro: el edificio activo, o el nombre de la app si no hay donde elegir.
       Se lleva el espacio sobrante y recorta con puntos suspensivos, que es lo
       unico que aguanta "Residencias Los Apamates de la Colina" en 390px. */
    .topbar-ctx {
        flex: 1 1 auto;
        min-width: 0;
        display: flex;
        align-items: center;
        justify-content: center;
        gap: .35rem;
        border: 0;
        background: none;
        padding: .25rem .5rem;
        color: #e2e8f0;
        font-size: .85rem;
        font-weight: 600;
        -webkit-tap-highlight-color: transparent;
    }

    button.topbar-ctx:active { color: #38bdf8; }

    .topbar-ctx .bi-buildings { color: #38bdf8; font-size: .9rem; flex: 0 0 auto; }
    .topbar-ctx .bi-chevron-down { font-size: .6rem; opacity: .65; flex: 0 0 auto; }

    .topbar-ctx-name {
        min-width: 0;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
    }

    .topbar-avatar {
        width: 32px;
        height: 32px;
        display: flex;
        align-items: center;
        justify-content: center;
        border-radius: 10px;
        background: linear-gradient(135deg, #1d4ed8, #0ea5e9);
        color: #fff;
        font-size: .72rem;
        font-weight: 700;
        letter-spacing: .01em;
    }

    /* ------------------------------------------------------- Lista de avisos
       Cada fila es un asunto, no un evento: dice cuantos hay, que son y a donde
       ir. La fila entera es el enlace; el "→ Revisar" esta para que se lea como
       una accion y no como un dato mas. */
    .notif-list {
        display: flex;
        flex-direction: column;
        gap: .5rem;
    }

    /* El tono NO se dice con una franja lateral de color (ver DESIGN.md,
       "Franjas de color prohibidas"): se dice con el chip de icono tintado y
       el borde completo teñido. Ademas el color ya venia dicho dos veces mas
       -en el icono y en la accion-, asi que la franja no informaba de nada
       que no estuviera ya en la tarjeta. */
    .notif-item {
        --t: #64748b;
        display: grid;
        grid-template-columns: auto minmax(0, 1fr);
        align-items: start;
        gap: .1rem .7rem;
        padding: .75rem .8rem;
        border: 1px solid color-mix(in srgb, var(--t) 26%, var(--app-border, #e2e8f0));
        border-radius: 12px;
        background: var(--app-surface, #fff);
        text-decoration: none;
        -webkit-tap-highlight-color: transparent;
    }

    .notif-item:active { background: var(--app-surface-soft, #f8fafc); }

    .notif-item--danger  { --t: var(--app-danger,  #dc2626); }
    .notif-item--warning { --t: var(--app-warning, #d97706); }
    .notif-item--info    { --t: var(--app-info,    #0284c7); }
    .notif-item--success { --t: var(--app-success, #16a34a); }

    .notif-ico {
        grid-row: 1 / span 2;
        width: 34px;
        height: 34px;
        display: flex;
        align-items: center;
        justify-content: center;
        border-radius: 10px;
        background: color-mix(in srgb, var(--t) 12%, #fff);
        color: var(--t);
        font-size: 1rem;
    }

    .notif-title {
        font-size: .92rem;
        font-weight: 700;
        color: var(--app-text, #0f172a);
        line-height: 1.25;
    }

    .notif-text {
        font-size: .78rem;
        color: var(--app-muted, #64748b);
        line-height: 1.35;
    }

    .notif-cta {
        grid-column: 2;
        margin-top: .3rem;
        font-size: .78rem;
        font-weight: 700;
        color: var(--t);
    }

    /* Vacio: el estado bueno. Se dice con esas palabras, no con un "sin
       resultados" que suena a que algo fallo. */
    .notif-empty {
        display: flex;
        flex-direction: column;
        align-items: center;
        gap: .45rem;
        padding: 2.2rem 1rem 2.6rem;
        text-align: center;
        color: var(--app-muted, #64748b);
    }

    .notif-empty i {
        font-size: 1.9rem;
        color: var(--app-success, #16a34a);
    }

    .notif-empty strong {
        font-size: .95rem;
        color: var(--app-text, #0f172a);
    }

    .notif-empty span { font-size: .8rem; }

    /* ------------------------------------------------------- Hoja de sesion */
    .sesion-list {
        display: flex;
        flex-direction: column;
        gap: .5rem;
    }

    .sesion-item {
        display: flex;
        align-items: center;
        gap: .7rem;
        padding: .85rem .8rem;
        border: 1px solid var(--app-border, #e2e8f0);
        border-radius: 12px;
        background: var(--app-surface, #fff);
        color: var(--app-text, #0f172a);
        font-size: .92rem;
        font-weight: 600;
        text-decoration: none;
        -webkit-tap-highlight-color: transparent;
    }

    .sesion-item:active { background: var(--app-surface-soft, #f8fafc); }
    .sesion-item i:first-child { font-size: 1.1rem; color: var(--app-muted, #64748b); }
    .sesion-item .bi-chevron-right { margin-left: auto; font-size: .75rem; opacity: .45; }

    /* Salir va aparte y en rojo: es el unico destino de la hoja del que no se
       vuelve con el boton "atras". */
    .sesion-item--salir {
        color: var(--app-danger, #dc2626);
        border-color: color-mix(in srgb, var(--app-danger, #dc2626) 30%, var(--app-border, #e2e8f0));
    }

    .sesion-item--salir i:first-child { color: var(--app-danger, #dc2626); }
}


/* ==========================================================================
   BARRA INFERIOR DE MOVIL + HOJAS (.tabsheet)
   Vivia incrustado en tabbar.php. Se movio aqui porque era
   identico en todas las paginas y al ir inline el navegador no podia
   reutilizarlo: reconstruia el CSSOM entero en cada navegacion.
   ========================================================================== */
/* La barra no existe en escritorio: nada de lo de aqui se aplica por encima de
   991.98px, ni siquiera el relleno del body. */
.tabbar,
.tabsheet { display: none; }

@media (max-width: 991.98px) {
    :root {
        /* Lo leen app.css (los avisos flotantes) y correos.php (el boton de
           redactar) para no quedar debajo de la barra. */
        --tabbar-h: 58px;
    }

    body {
        padding-bottom: calc(var(--tabbar-h) + env(safe-area-inset-bottom, 0px));
    }

    /* Ese relleno del body queda DEBAJO de la barra: sirve para que el scroll
       llegue al final, no para dar aire. El aire visible lo pone la pagina, y
       hace falta un poco mas del habitual porque el boton redondo de "Pagar"
       sobresale por encima del borde de la barra. */
    .app-page {
        padding-bottom: 2.75rem;
    }

    .tabbar {
        position: fixed;
        left: 0;
        right: 0;
        bottom: 0;
        z-index: 1035; /* sobre el contenido, debajo del backdrop de los modales (1050) */
        display: grid;
        grid-auto-flow: column;
        grid-auto-columns: 1fr;
        align-items: stretch;
        height: calc(var(--tabbar-h) + env(safe-area-inset-bottom, 0px));
        padding-bottom: env(safe-area-inset-bottom, 0px);

        /* TRANSPARENTE, CON UNA SOLA LINEA.
           Era una franja azul oscuro con sombra: media pantalla de movil
           gastada en un elemento que no se lee, solo se toca. Ahora se ve la
           pagina por debajo y lo unico que la separa es un pelo de 1px, el
           mismo borde que usan las tarjetas.

           El desenfoque no es decoracion: sin fondo, el texto de la pagina
           pasa POR DETRAS de los iconos al desplazarse y se vuelven
           ilegibles justo cuando hay que tocarlos. El desenfoque deja ver
           que hay algo debajo -que es lo que se pidio- sin que compita. */
        background: transparent;
        border-top: 1px solid var(--app-border);
        backdrop-filter: blur(14px) saturate(140%);
        -webkit-backdrop-filter: blur(14px) saturate(140%);
    }

    /* Sin desenfoque (Firefox de Android hasta hace poco, WebView viejo) el
       fondo transparente dejaria los iconos sobre el texto de la pagina. Ahi
       se cae a un velo del color del fondo: sigue viendose translucido y
       sigue siendo legible. */
    @supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
        .tabbar { background: rgba(245, 247, 251, .92); }
    }

    .tabbar-item {
        position: relative;
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        gap: 2px;
        border: 0;
        background: none;
        padding: 0 2px;
        color: var(--app-muted);
        font-size: .66rem;
        font-weight: 600;
        letter-spacing: -.01em;
        text-decoration: none;
        -webkit-tap-highlight-color: transparent;
        transition: color .12s ease;
    }

    .tabbar-item i {
        font-size: 1.28rem;
        line-height: 1;
    }

    .tabbar-item:active { color: var(--app-text); }

    .tabbar-item.is-active { color: var(--app-primary); }

    /* Marca de "estas aqui" pegada al borde superior. Un subrayado arriba se lee
       antes que el cambio de color, que a pleno sol casi no se nota. */
    .tabbar-item.is-active::before {
        content: "";
        position: absolute;
        top: 0;
        left: 50%;
        transform: translateX(-50%);
        width: 26px;
        height: 3px;
        border-radius: 0 0 3px 3px;
        background: var(--app-primary);
    }

    .tabbar-badge {
        position: absolute;
        top: 5px;
        left: calc(50% + 5px);
        min-width: 17px;
        height: 17px;
        padding: 0 4px;
        border-radius: 999px;
        background: #ef4444;
        color: #fff;
        font-size: .6rem;
        font-weight: 700;
        line-height: 17px;
        text-align: center;
    }

    .tabbar-dot {
        position: absolute;
        top: 9px;
        left: calc(50% + 7px);
        width: 8px;
        height: 8px;
        border-radius: 999px;
        background: #ef4444;
    }

    /* El unico boton elevado. Solo el residente lo tiene, y solo para pagar.

       La etiqueta era blanca porque debajo habia una franja azul oscuro. Al
       quitarla se quedaba escrita en blanco sobre fondo claro, es decir,
       invisible: el boton mas importante de la pantalla del residente sin
       nombre. Ahora la etiqueta va en el azul del boton y el blanco se queda
       donde de verdad hace falta, dentro del circulo. */
    .tabbar-item.is-cta {
        color: var(--app-primary);
        justify-content: flex-end;
        padding-bottom: 6px;
    }

    .tabbar-item.is-cta i {
        color: #fff;
        display: flex;
        align-items: center;
        justify-content: center;
        width: 46px;
        height: 46px;
        margin-top: -20px;
        margin-bottom: 2px;
        border-radius: 999px;
        background: linear-gradient(180deg, #3b82f6, #2563eb);
        box-shadow: 0 6px 16px rgba(37, 99, 235, .45);
        font-size: 1.25rem;
    }

    .tabbar-item.is-cta::before { content: none; }

    /* ---------------------------------------------------------------- Hoja */

    .tabsheet {
        display: block;
        position: fixed;
        inset: 0;
        z-index: 1040;
        visibility: hidden;
        pointer-events: none;
    }

    .tabsheet.is-open {
        visibility: visible;
        pointer-events: auto;
    }

    .tabsheet-veil {
        position: absolute;
        inset: 0;
        background: rgba(15, 23, 42, .5);
        opacity: 0;
        transition: opacity .18s ease;
    }

    .tabsheet.is-open .tabsheet-veil { opacity: 1; }

    .tabsheet-panel {
        position: absolute;
        left: 0;
        right: 0;
        bottom: 0;
        max-height: 92vh;
        max-height: 92dvh;
        display: flex;
        flex-direction: column;
        border-radius: 18px 18px 0 0;
        background: var(--app-surface, #fff);
        box-shadow: 0 -12px 40px rgba(15, 23, 42, .28);
        transform: translateY(100%);
        transition: transform .22s cubic-bezier(.32, .72, 0, 1);
    }

    .tabsheet.is-open .tabsheet-panel { transform: none; }

    .tabsheet-grabber {
        width: 38px;
        height: 4px;
        margin: .5rem auto .1rem;
        border-radius: 999px;
        background: var(--app-border, #e2e8f0);
        flex: 0 0 auto;
    }

    .tabsheet-head {
        display: flex;
        align-items: center;
        gap: .6rem;
        padding: .5rem 1rem .65rem;
        flex: 0 0 auto;
    }

    .tabsheet-avatar {
        width: 38px;
        height: 38px;
        flex: 0 0 auto;
        display: flex;
        align-items: center;
        justify-content: center;
        border-radius: 12px;
        background: linear-gradient(135deg, #1d4ed8, #0ea5e9);
        color: #fff;
        font-size: .82rem;
        font-weight: 700;
    }

    .tabsheet-who {
        min-width: 0;
        line-height: 1.2;
    }

    .tabsheet-name {
        display: block;
        font-size: .95rem;
        font-weight: 700;
        color: var(--app-text, #0f172a);
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
    }

    .tabsheet-role {
        display: block;
        font-size: .74rem;
        color: var(--app-muted, #64748b);
    }

    .tabsheet-close {
        margin-left: auto;
        width: 38px;
        height: 38px;
        flex: 0 0 auto;
        display: flex;
        align-items: center;
        justify-content: center;
        border: 1px solid var(--app-border, #e2e8f0);
        border-radius: 999px;
        background: var(--app-surface-soft, #f8fafc);
        color: var(--app-muted, #64748b);
        font-size: 1rem;
    }

    .tabsheet-body {
        overflow-y: auto;
        overscroll-behavior: contain;
        -webkit-overflow-scrolling: touch;
        /* El colchon de abajo lo ponia el pie, que ya no existe. Sin el, la
           ultima fila quedaba pegada al borde -y bajo la barra de gestos de los
           telefonos sin boton fisico-. Lo heredan las tres hojas. */
        padding: 0 1rem calc(.9rem + env(safe-area-inset-bottom, 0px));
        flex: 1 1 auto;
    }

    .tabsheet-tools {
        display: flex;
        flex-direction: column;
        gap: .5rem;
        padding-bottom: .85rem;
    }

    /* El buscador de la hoja hereda el CSS de `.global-search` del navbar, que
       ahi esta pensado para fondo oscuro. Aqui se devuelve a claro. */
    .tabsheet .global-search {
        position: relative;
        width: 100%;
        min-width: 0;
        max-width: none;
    }

    .tabsheet .global-search .form-control,
    .tabsheet .global-search .input-group-text,
    .tabsheet .building-switch .form-select,
    .tabsheet .building-switch .input-group-text {
        background: var(--app-surface-soft, #f8fafc);
        border-color: var(--app-border, #e2e8f0);
        color: var(--app-text, #0f172a);
    }

    .tabsheet .global-search .form-control::placeholder {
        color: var(--app-muted, #94a3b8);
    }

    .tabsheet .global-search-results {
        left: 0;
        right: 0;
        width: 100%;
        max-height: 46vh;
    }

    /* Filtro de secciones. Deliberadamente NO parece el buscador global de
       arriba: sin lupa y pegado a la rejilla, para que se lea como "filtra esta
       lista" y no como "busca en el sistema". */
    .tabsheet-filter {
        position: relative;
        margin-bottom: .6rem;
    }

    .tabsheet-filter input {
        width: 100%;
        min-height: 42px;
        padding: .5rem 2.4rem .5rem .85rem;
        border: 1px solid var(--app-border, #e2e8f0);
        border-radius: 10px;
        background: var(--app-surface-soft, #f8fafc);
        color: var(--app-text, #0f172a);
        font-size: 16px; /* menos de 16 y iOS hace zoom al enfocar */
        font-weight: 500;
    }

    .tabsheet-filter input:focus {
        outline: none;
        border-color: var(--app-primary, #2563eb);
        box-shadow: 0 0 0 .2rem rgba(37, 99, 235, .12);
    }

    .tabsheet-filter-clear {
        position: absolute;
        top: 50%;
        right: .3rem;
        transform: translateY(-50%);
        width: 36px;
        height: 36px;
        display: flex;
        align-items: center;
        justify-content: center;
        border: 0;
        border-radius: 999px;
        background: none;
        color: var(--app-muted, #64748b);
        font-size: .8rem;
    }

    .tabsheet-nada {
        margin: .35rem 0 0;
        padding: 1.25rem .5rem;
        text-align: center;
        color: var(--app-muted, #64748b);
        font-size: .85rem;
    }

    .tabsheet-group[hidden],
    .tabsheet-tile[hidden] { display: none !important; }

    .tabsheet-group + .tabsheet-group { margin-top: .9rem; }

    .tabsheet-title {
        font-size: .68rem;
        font-weight: 700;
        letter-spacing: .08em;
        text-transform: uppercase;
        color: var(--app-muted, #64748b);
        margin-bottom: .45rem;
    }

    .tabsheet-grid {
        display: grid;
        grid-template-columns: repeat(3, 1fr);
        gap: .5rem;
    }

    .tabsheet-tile {
        position: relative;
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: flex-start;
        gap: .35rem;
        min-height: 82px;
        padding: .7rem .35rem .6rem;
        border: 1px solid var(--app-border, #e2e8f0);
        border-radius: 14px;
        background: var(--app-surface, #fff);
        color: var(--app-text, #0f172a);
        font-size: .72rem;
        font-weight: 600;
        line-height: 1.15;
        text-align: center;
        text-decoration: none;
        -webkit-tap-highlight-color: transparent;
    }

    .tabsheet-tile:active {
        background: var(--app-surface-soft, #f8fafc);
        transform: scale(.97);
    }

    .tabsheet-tile i {
        font-size: 1.22rem;
        line-height: 1;
        color: var(--app-primary, #2563eb);
    }

    .tabsheet-tile.is-active {
        border-color: var(--app-primary, #2563eb);
        background: rgba(37, 99, 235, .06);
    }

    .tabsheet-tile .tabbar-badge {
        top: 6px;
        left: auto;
        right: 6px;
    }

    body.tabsheet-open { overflow: hidden; }
}

@media (prefers-reduced-motion: reduce) {
    .tabsheet-panel,
    .tabsheet-veil { transition: none; }
    .tabsheet-tile:active { transform: none; }
}

/* ==========================================================================
   Verificacion en dos pasos (codigo de 6 digitos por correo)
   ==========================================================================
   Vive aqui y no en la vista porque ya son dos las pantallas que la piden —el
   perfil, para cambiar correo o contrasena, y Edificios, para cambiar la cuenta
   bancaria— y las dos tienen que verse igual: si el bloque cambiara de aspecto
   entre una y otra, dejaria de leerse como "el mismo trance de seguridad" y
   pasaria a leerse como un campo mas del formulario. */
.verify-box {
    border: 1px solid var(--app-border);
    border-radius: 14px;
    background: var(--app-surface-soft);
    padding: 1rem;
}
.verify-head { display: flex; align-items: flex-start; gap: .75rem; margin-bottom: .85rem; }
.verify-ico {
    width: 36px; height: 36px; flex: 0 0 auto;
    border-radius: 10px; display: inline-grid; place-items: center;
    background: rgba(37, 99, 235, .12); color: var(--app-primary); font-size: 1rem;
}
.verify-title { font-weight: 800; font-size: .95rem; color: var(--app-text); line-height: 1.25; }
.verify-text { font-size: .82rem; color: var(--app-muted); line-height: 1.5; margin-top: .15rem; }
.verify-row { display: flex; gap: .5rem; align-items: center; flex-wrap: wrap; }
.verify-input {
    max-width: 170px;
    text-align: center;
    font-family: "SF Mono", "Roboto Mono", Menlo, Consolas, monospace;
    font-weight: 800;
    font-size: 1.15rem;
    letter-spacing: .35em;
    text-indent: .35em;   /* compensa el tracking para que se vea centrado */
    padding-block: .5rem;
}
.verify-input:disabled { background: var(--app-border-soft); letter-spacing: normal; text-indent: 0; }
.verify-feedback { font-size: .8rem; margin-top: .6rem; min-height: 1.25rem; line-height: 1.45; color: var(--app-muted); }
.verify-feedback.is-ok   { color: var(--app-success); }
.verify-feedback.is-warn { color: var(--app-warning); }
.verify-feedback.is-err  { color: var(--app-danger); }
.verify-countdown { font-variant-numeric: tabular-nums; font-weight: 700; }

@media (prefers-reduced-motion: reduce) {
    .verify-box, .verify-input { transition: none; }
}


/* ==========================================================================
   POLITICAS LEGALES
   --------------------------------------------------------------------------
   Se usan en tres sitios -la ventana que se abre desde el menu, la pagina
   /politicas.php y la pantalla de aceptacion del administrador-, asi que viven
   aqui y no en ninguna de las tres. El texto en si lo estiliza
   .auth-modal-legal (auth.css), que ya estaba resuelto para los modales del
   login y no habia razon para duplicarlo.
   ========================================================================== */

/* Cada documento es una tarjeta propia. Corridos, con una linea fina entre
   ellos, el segundo empieza a varias pantallas del primero y se lee como
   continuacion del mismo texto: la gente termina el primero y se va sin saber
   que hay otro. */
.legal-doc {
  background: var(--app-surface);
  border: 1px solid var(--app-border);
  border-radius: var(--card-radius, 14px);
  padding: 1.5rem;
}

.legal-doc + .legal-doc { margin-top: 1.25rem; }

.legal-doc h2 {
  font-size: 1.1rem;
  font-weight: 800;
  letter-spacing: -.01em;
  color: var(--app-text);
  margin-bottom: 0;
}

/* Pestañas de la ventana: mismo problema resuelto al reves. Ahi no caben dos
   tarjetas largas, asi que lo que se muestra de entrada es que SON dos. */
.legal-tabs {
  border-bottom: 1px solid var(--app-border);
  gap: .25rem;
  flex-wrap: nowrap;
  overflow-x: auto;
  scrollbar-width: none;
}

.legal-tabs::-webkit-scrollbar { display: none; }

.legal-tabs .nav-link {
  border: 0;
  border-bottom: 2px solid transparent;
  border-radius: 0;
  padding: .55rem .85rem;
  font-size: .88rem;
  font-weight: 700;
  color: var(--app-muted);
  white-space: nowrap;
}

.legal-tabs .nav-link:hover { color: var(--app-text); }

.legal-tabs .nav-link.active {
  color: var(--app-primary);
  background: transparent;
  border-bottom-color: var(--app-primary);
}

.legal-tabs .nav-link:focus-visible {
  outline: 2px solid var(--app-primary);
  outline-offset: -2px;
}

.legal-tab-content { padding-top: 1.1rem; }

@media (max-width: 575.98px) {
  .legal-doc { padding: 1.15rem; }
  .legal-tabs .nav-link { font-size: .82rem; padding: .5rem .6rem; }
}

/* La constancia del propio usuario: que version se le mostro y cuando. Va
   arriba y sobre fondo distinto porque es lo unico de esta pantalla que habla
   de EL; los documentos son iguales para todos. */
.legal-record {
  background: var(--app-surface-soft);
  border: 1px solid var(--app-border);
  border-radius: var(--card-radius, 14px);
  padding: 1.15rem 1.25rem;
  margin-bottom: 1.75rem;
}

.legal-record h2 {
  font-size: .95rem;
  font-weight: 700;
  color: var(--app-text);
  margin-bottom: .35rem;
}

.legal-record > p {
  font-size: .85rem;
  color: var(--app-muted);
  max-width: 66ch;
  margin-bottom: .9rem;
}

/* La tabla se desborda antes que la pagina: cuatro columnas no caben en un
   telefono estrecho y el cuerpo del documento no tiene por que moverse. */
.legal-record-wrap { overflow-x: auto; }

.legal-record table {
  width: 100%;
  font-size: .84rem;
  border-collapse: collapse;
}

.legal-record th {
  text-align: left;
  font-weight: 600;
  color: var(--app-muted);
  font-size: .72rem;
  text-transform: uppercase;
  letter-spacing: .06em;
  padding: 0 .75rem .4rem 0;
  border-bottom: 1px solid var(--app-border);
  white-space: nowrap;
}

.legal-record td {
  padding: .5rem .75rem .5rem 0;
  border-bottom: 1px solid var(--app-border-soft);
  color: var(--app-text);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.legal-record tr:last-child td { border-bottom: 0; }

.legal-mode {
  display: inline-block;
  font-size: .68rem;
  font-weight: 700;
  letter-spacing: .04em;
  text-transform: uppercase;
  padding: .15rem .45rem;
  border-radius: 4px;
}

.legal-mode.aceptada   { color: #065f46; background: #d1fae5; }
.legal-mode.notificada { color: #1e40af; background: #dbeafe; }

/* Estado de carga del contenido, que se pide al abrir y no viaja en cada
   pagina. */
.legal-loading {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: .6rem;
  min-height: 8rem;
  color: var(--app-muted);
  font-size: .9rem;
}

/* Pie de la ventana de aceptacion: la casilla y los botones van en dos filas,
   no en la fila unica que trae el modal por defecto. El texto de la casilla es
   una declaracion de tres lineas -no un "acepto" suelto- y comprimirlo al lado
   del boton lo dejaba en una columna de cuatro palabras de ancho. */
.legal-gate-foot {
  display: block;
  padding-top: 1rem;
}

.legal-gate-check {
  display: flex;
  align-items: flex-start;
  gap: .75rem;
  margin-bottom: 1rem;
}

.legal-gate-check input {
  flex: none;
  margin-top: .2rem;
  width: 1.15rem;
  height: 1.15rem;
}

.legal-gate-check label {
  font-size: .88rem;
  line-height: 1.55;
  color: var(--app-text);
  text-align: left;
}

.legal-gate-actions {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
}

.legal-gate-exit {
  font-size: .85rem;
  color: var(--app-muted);
  text-decoration: none;
}

.legal-gate-exit:hover { color: var(--app-danger); }

@media (max-width: 575.98px) {
  .legal-gate-actions { flex-direction: column-reverse; align-items: stretch; }
  .legal-gate-actions .btn { width: 100%; }
  .legal-gate-exit { text-align: center; padding: .35rem 0; }
}

/* ==========================================================================
   MEDIDOR DE CONTRASEÑA
   --------------------------------------------------------------------------
   Informa, no bloquea: el minimo del sistema siguen siendo 6 caracteres. La
   barra da el vistazo y el texto da el motivo; el nivel se lee con palabras
   ademas de con color, porque un rojo y un verde no le dicen nada a quien no
   los distingue.
   ========================================================================== */

.pw-strength {
  margin-top: .45rem;
}

.pw-strength-bar {
  height: 4px;
  border-radius: 999px;
  background: var(--app-border);
  overflow: hidden;
}

.pw-strength-bar > span {
  display: block;
  height: 100%;
  width: 0;
  border-radius: inherit;
  background: var(--app-muted);
  transition: width .2s ease, background-color .2s ease;
}

.pw-strength-text {
  margin: .3rem 0 0;
  font-size: .78rem;
  line-height: 1.45;
  color: var(--app-muted);
  min-height: 1.15rem; /* Reserva el hueco: sin esto el formulario da un salto
                          en cuanto se escribe la primera letra. */
}

.pw-strength[data-nivel="muy-debil"] > .pw-strength-bar > span { background: var(--app-danger); }
.pw-strength[data-nivel="debil"]     > .pw-strength-bar > span { background: #f97316; }
.pw-strength[data-nivel="aceptable"] > .pw-strength-bar > span { background: var(--app-warning); }
.pw-strength[data-nivel="buena"]     > .pw-strength-bar > span { background: #65a30d; }
.pw-strength[data-nivel="fuerte"]    > .pw-strength-bar > span { background: var(--app-success); }

.pw-strength[data-nivel="muy-debil"] > .pw-strength-text { color: var(--app-danger); }
.pw-strength[data-nivel="fuerte"]    > .pw-strength-text { color: var(--app-success); }

@media (prefers-reduced-motion: reduce) {
  .pw-strength-bar > span { transition: none; }
}

/* ==========================================================================
   GESTOS Y CONTINUIDAD EN TELEFONO
   ==========================================================================
   Lo que separa "una web que carga" de "una aplicacion": que las pantallas se
   encadenen en vez de parpadear, que el pulgar pueda actuar sin apuntar a un
   boton, y que tirar hacia abajo signifique algo. Todo vive por debajo de
   992px salvo la transicion entre paginas, que es inofensiva en cualquier
   ancho pero se desactiva con movimiento reducido.
   ========================================================================== */

/* --- Transiciones entre paginas -------------------------------------------
   El fogonazo blanco al cambiar de pantalla es el delator numero uno de que
   esto es web y no una aplicacion. `navigation: auto` encadena las dos
   paginas con un fundido; los navegadores que no la soportan siguen
   navegando como siempre, sin nada que arreglar.
   Se limita a telefono porque en escritorio la navegacion ya se percibe
   instantanea y ahi el fundido solo añade latencia aparente. */
@media (max-width: 991.98px) {
  @view-transition { navigation: auto; }
}

/* El chasis no viaja: la barra inferior y la superior son el marco fijo
   dentro del que cambia el contenido. Sin esto se desvanecen y reaparecen en
   cada navegacion, que es justo lo contrario de lo que se busca. */
@media (max-width: 991.98px) {
  .tabbar { view-transition-name: app-tabbar; }
  .topbar-movil { view-transition-name: app-topbar; }
}

@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) {
    animation: none !important;
  }
}

/* --- Tirar para refrescar --------------------------------------------------
   `overscroll-behavior-y: contain` apaga el gesto nativo de Chrome Android:
   sin esto disparan los dos y la pagina se recarga por duplicado. En la
   aplicacion instalada no habia ninguno de los dos, que era el problema. */
@media (max-width: 991.98px) {
  html { overscroll-behavior-y: contain; }
}

.ptr {
  position: fixed;
  top: 8px;
  left: 50%;
  z-index: 1032;               /* sobre el contenido, bajo las hojas (1040) */
  width: 34px;
  height: 34px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--app-surface);
  border: 1px solid var(--app-border);
  box-shadow: var(--app-shadow-sm);
  color: var(--app-muted);
  font-size: .95rem;
  opacity: 0;
  transform: translate(-50%, 0);
  pointer-events: none;
}

/* Pasado el umbral, la flecha se da la vuelta: el indicador dice "sueltame"
   antes de que sueltes, no despues. */
.ptr > .bi { transition: transform .18s ease; }
.ptr.is-listo { color: var(--app-primary); border-color: var(--app-primary); }
.ptr.is-listo > .bi { transform: rotate(180deg); }
.ptr.is-girando > .bi { animation: ptr-giro .8s linear infinite; }

@keyframes ptr-giro {
  to { transform: rotate(360deg); }
}

/* --- Deslizar una fila para actuar -----------------------------------------
   El fondo verde asoma por debajo de la fila segun se desplaza; no es un
   elemento aparte, es el contenedor el que lo enseña. Asi no hay dos nodos
   que mantener sincronizados. */
[data-swipe-action] {
  position: relative;
  background: var(--app-surface);
  touch-action: pan-y;         /* el navegador conserva el scroll vertical */
}

[data-swipe-action].is-swiping {
  transition: none;
  user-select: none;
}

.swipe-host { position: relative; overflow: hidden; }

.swipe-host::before {
  content: "\f633";            /* bi-check-lg, tal como lo numera el paquete que traemos */
  font-family: bootstrap-icons !important;
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 100%;
  display: flex;
  align-items: center;
  padding-left: 1.15rem;
  background: var(--app-success);
  color: #fff;
  font-size: 1.15rem;
  opacity: 0;
  transition: opacity .15s ease;
  pointer-events: none;
}

.swipe-host:has(> .is-swiping)::before { opacity: 1; }
.swipe-host:has(> .is-armed)::before { padding-left: 1.6rem; }

@media (prefers-reduced-motion: reduce) {
  .ptr > .bi,
  .swipe-host::before { transition: none; }
  .ptr.is-girando > .bi { animation: none; }
}

/* ==========================================================================
   ACCIONES DE FILA  ·  .act-group / .act-btn
   ==========================================================================
   El problema que resuelve: las filas de las tablas venian con cinco botones
   de cinco colores saturados distintos -gris, azul, verde, amarillo, rojo- y
   ninguno mandaba. Un semaforo entero por fila, repetido treinta veces, es
   ruido: el ojo no puede priorizar cuando todo grita.

   La regla aqui es jerarquia, no color:

     .act-btn              neutro. El grueso de las acciones (ver, enviar,
                           editar) no necesita color: se reconocen por su
                           icono y por su sitio.
     .act-btn.is-primary   UNA por fila como mucho. Es la accion que la fila
                           existe para ofrecer.
     .act-btn.is-money     variante de la anterior para cobrar. Verde porque
                           el dinero que entra es la unica semantica que el
                           usuario ya tiene aprendida en este sistema.
     .act-btn.is-danger    neutra en reposo, roja al apuntarla. Borrar no
                           tiene por que ir gritando desde cada fila; tiene
                           que avisar cuando vas a pulsarlo.

   Sin franjas ni bordes de color como adorno (DESIGN.md): el color entra por
   el relleno de la accion principal y por el estado de foco.
   ========================================================================== */

.act-group {
  display: inline-flex;
  align-items: center;
  justify-content: flex-end;
  gap: .35rem;
  white-space: nowrap;
}

.act-btn {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: .3rem;
  width: 36px;
  height: 36px;
  padding: 0;
  border-radius: 9px;
  border: 1px solid var(--app-border);
  background: var(--app-surface);
  color: var(--app-muted);
  font-size: .92rem;
  font-weight: 700;
  line-height: 1;
  text-decoration: none;
  -webkit-tap-highlight-color: transparent;
  transition: background .15s ease, border-color .15s ease, color .15s ease;
}

/* Con texto al lado deja de ser un cuadrado y crece a lo ancho. */
.act-btn.has-label {
  width: auto;
  padding: 0 .7rem;
  font-size: .82rem;
}

.act-btn:hover,
.act-btn:focus-visible {
  background: var(--app-surface-soft);
  border-color: #cbd5e1;
  color: var(--app-text);
}

.act-btn:focus-visible {
  outline: 2px solid var(--app-primary);
  outline-offset: 1px;
}

.act-btn:active { transform: scale(.96); }

.act-btn.is-primary,
.act-btn.is-money {
  color: #fff;
  border-color: transparent;
}
.act-btn.is-primary { background: var(--app-primary); }
.act-btn.is-money   { background: var(--app-success); }

.act-btn.is-primary:hover,
.act-btn.is-primary:focus-visible {
  background: #1d4ed8;
  border-color: transparent;
  color: #fff;
}
.act-btn.is-money:hover,
.act-btn.is-money:focus-visible {
  background: #047857;
  border-color: transparent;
  color: #fff;
}

/* Destructiva: callada hasta que la apuntas. */
.act-btn.is-danger:hover,
.act-btn.is-danger:focus-visible {
  background: #fef2f2;
  border-color: var(--app-danger);
  color: var(--app-danger);
}

.act-btn:disabled,
.act-btn.disabled {
  opacity: .38;
  pointer-events: none;
}

/* En telefono el dedo pide mas superficie que el raton: 40px es el minimo
   comodo, y el grupo se alinea a la izquierda porque ahi empieza la lectura
   de la tarjeta. */
@media (max-width: 991.98px) {
  .act-group {
    justify-content: flex-start;
    gap: .4rem;
    flex-wrap: wrap;
    white-space: normal;
  }

  .act-btn {
    width: 40px;
    height: 40px;
    font-size: 1rem;
  }

  .act-btn.has-label {
    width: auto;
    padding: 0 .85rem;
    font-size: .86rem;
  }
}

@media (prefers-reduced-motion: reduce) {
  .act-btn { transition: none; }
  .act-btn:active { transform: none; }
}

/* Descarga en curso: el archivo viaja por fetch, asi que sin esta senal el
   boton se ve inerte y el usuario vuelve a hacer clic. */
a[download].is-downloading {
  opacity: 0.6;
  pointer-events: none;
  cursor: progress;
}

/* ==========================================================================
   PANEL DEL RESIDENTE
   --------------------------------------------------------------------------
   Antes vivia como un bloque <style> de 260 lineas dentro de
   app/Views/user/dashboard.php, con su propio sistema de tarjetas
   (.unit-card, .news-card, .mobile-payment-card) y hexadecimales sueltos, y
   con el contenido impreso DOS veces: una version de escritorio y otra de
   movil escondida con d-none. Aqui hay un solo juego de piezas, mobile-first,
   que a partir de 992px se reparte en dos columnas.

   Se reserva para esta pantalla lo que de verdad es de esta pantalla; el
   resto (superficies, bordes, sombras, color de estado) sale de los tokens.
   ========================================================================== */

.res-page {
  display: grid;
  gap: .85rem;
}

/* -------------------------------------------------- saludo con contexto */
/*
 * Va dentro de `.app-hero`, como el resto de las pantallas. La linea de
 * contexto (unidad, edificio, alicuota) resume lo que la tarjeta "Mis
 * unidades" desarrolla mas abajo, para que el dato este arriba sin costar
 * una pantalla.
 */
.res-greet {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: .75rem;
}

.res-greet-context {
  margin: .2rem 0 0;
  font-size: .82rem;
  color: var(--app-muted);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .35rem;
}

.res-greet-context strong {
  color: var(--app-text);
  font-weight: 700;
}

.res-greet-context .res-sep {
  color: var(--app-border);
}

/* ------------------------------------------------------ tarjeta de saldo */
/*
 * El panel anterior dibujaba esta tarjeta como una tarjeta de debito, y era lo
 * mejor que tenia: se reconocia al instante como "lo del dinero". Aqui vuelve
 * ese lenguaje sobre el contenido nuevo.
 *
 * Lo que NO vuelve es su problema. Aquella tarjeta ponia texto de color sobre
 * fondo de color (el rotulo ambar sobre ambar translucido no llegaba a 4,5:1,
 * y el publico de esta pantalla son propietarios mayores leyendo a la luz del
 * dia). Aqui la cara solo lleva blanco sobre casi negro, que pasa siempre, y
 * el color del estado vive abajo sobre blanco, donde se puede medir.
 *
 * Tampoco se copia la geometria: una tarjeta real es un objeto de tamano fijo
 * con cinco datos impresos, y esta sostiene ademas el chip de vencimiento, la
 * barra de composicion, la frase, el boton y el rastro del pago. Se toma
 * prestado el vocabulario (chip, contactless, cifra grabada, rotulos en
 * versalita), no la proporcion.
 *
 * --res-tint la sigue eligiendo data-state; ahora tine la pildora y la barra,
 * no la superficie. Nunca una franja lateral: eso sigue prohibido.
 */
.saldo-card {
  --res-tint: var(--app-warning);
  position: relative;
  border-radius: var(--card-radius);
  border: 1px solid var(--app-border);
  background: var(--app-surface);
  box-shadow: var(--app-shadow-sm);
  overflow: hidden;
}

.saldo-card[data-state="vencido"] { --res-tint: var(--app-danger); }
.saldo-card[data-state="aldia"]   { --res-tint: var(--app-success); }

/* ---------------------------------------------------------------- la cara */
.saldo-face {
  position: relative;
  padding: 1rem 1rem .9rem;
  color: #fff;
  /* Casi el negro del texto con una gota del azul de marca: es la misma tinta
     de la barra de navegacion, no un color nuevo. */
  background: linear-gradient(150deg,
    color-mix(in srgb, var(--app-text) 86%, var(--app-primary)),
    var(--app-text) 62%);
}

/* Relieve: una tarjeta coge la luz. Una sola pasada muy tenue, en diagonal. */
.saldo-face::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(112deg, rgba(255, 255, 255, .13) 0 38%, rgba(255, 255, 255, 0) 62%);
}

/* El contenido va por encima del brillo, o lo lavaria. */
.saldo-face > * {
  position: relative;
  z-index: 1;
}

.saldo-face-top {
  display: flex;
  align-items: center;
  gap: .55rem;
  margin-bottom: .85rem;
}

/*
 * El chip. Es la pieza que hace que algo se lea como tarjeta antes que
 * cualquier otra, y su oro es un MATERIAL, no un color de marca: por eso vive
 * aqui como variable local y no toca la paleta.
 */
.saldo-chip {
  --chip-luz: #e6d29a;
  --chip-sombra: #a98c46;
  width: 32px;
  height: 24px;
  flex: none;
  position: relative;
  border-radius: 5px;
  background: linear-gradient(145deg, var(--chip-luz), var(--chip-sombra));
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .15);
}

/* Los dos contactos del chip. */
.saldo-chip::before,
.saldo-chip::after {
  content: "";
  position: absolute;
  left: 5px;
  right: 5px;
  height: 1px;
  background: rgba(0, 0, 0, .22);
}

.saldo-chip::before { top: 8px; }
.saldo-chip::after { bottom: 8px; }

/* El contactless es el icono de wifi girado: son las mismas ondas. */
.saldo-nfc {
  font-size: .95rem;
  opacity: .75;
  transform: rotate(90deg);
}

/* Quien emite: el edificio, o cuantos son cuando el propietario esta en varios. */
.saldo-issuer {
  margin-left: auto;
  min-width: 0;
  font-size: .66rem;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: .09em;
  opacity: .75;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/*
 * Los rotulos al pie, como el titular y el "valida hasta". La unidad hace de
 * numero de cuenta; con varias, la primera y cuantas mas (el listado completo
 * va en el title, porque en la cara no cabe).
 */
.saldo-face-foot {
  display: flex;
  gap: 1.4rem;
  margin-top: 1rem;
  font-size: .82rem;
  font-weight: 700;
}

.saldo-face-foot > span {
  display: flex;
  flex-direction: column;
  gap: .1rem;
  min-width: 0;
}

.saldo-face-foot i {
  font-style: normal;
  font-size: .58rem;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: .11em;
  opacity: .62;
}

.saldo-face-foot b {
  font-weight: 700;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* --------------------------------------------------------------- el cuerpo */
.saldo-body {
  padding: .9rem 1rem 1rem;
}

/* Vencimiento y alcance: lo que matiza la cifra de arriba. */
.saldo-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .5rem;
  margin-bottom: .2rem;
}

.saldo-scope {
  font-size: .76rem;
  font-weight: 600;
  color: var(--app-muted);
}

.saldo-label {
  font-size: .72rem;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: .05em;
  opacity: .72;
}

/* Cuando vence. El dato que el panel no daba en ninguna parte. */
.saldo-due {
  display: inline-flex;
  align-items: center;
  gap: .3rem;
  flex: none;
  padding: .3rem .6rem;
  border-radius: 999px;
  background: color-mix(in srgb, var(--res-tint) 12%, white);
  border: 1px solid color-mix(in srgb, var(--res-tint) 30%, white);
  color: var(--res-tint);
  font-size: .72rem;
  font-weight: 700;
}

/* La cifra "grabada": tabular y con el tracking abierto de un numero impreso
   en una tarjeta. El apretado de titular (-.035em) la hacia leerse como un
   titulo, que es justo lo que no es. */
.saldo-amount {
  margin-top: .3rem;
  font-size: clamp(2.2rem, 9vw, 2.6rem);
  font-weight: 800;
  line-height: 1.02;
  letter-spacing: .005em;
  font-variant-numeric: tabular-nums;
}

.saldo-amount .saldo-cur {
  font-size: .58em;
  font-weight: 700;
  opacity: .65;
  vertical-align: .3em;
  margin-right: .05em;
}

/* Como se reparte lo que debe: en revision + por pagar = total. */
.saldo-split {
  margin-top: .9rem;
}

/* Sin pildora de vencimiento arriba (al dia, o recibos sin fecha de corte) lo
   primero del cuerpo ya no necesita separarse de nada. */
.saldo-body > :first-child {
  margin-top: 0;
}

.saldo-bar {
  display: flex;
  gap: 2px;
  height: 9px;
  border-radius: 999px;
  overflow: hidden;
  background: var(--app-border-soft);
}

/* El rayado distingue "en revision" de "por pagar" sin depender del color:
   no todo el mundo separa ambar de rojo. */
.saldo-seg-review {
  background: repeating-linear-gradient(
    -45deg,
    var(--app-warning) 0 4px,
    color-mix(in srgb, var(--app-warning) 55%, white) 4px 8px
  );
}

.saldo-seg-due { background: var(--app-danger); }

.saldo-legend {
  display: flex;
  flex-wrap: wrap;
  gap: .35rem .9rem;
  margin-top: .55rem;
  font-size: .76rem;
  color: var(--app-muted);
}

.saldo-legend > span {
  display: inline-flex;
  align-items: center;
  gap: .35rem;
}

.saldo-legend i {
  width: 9px;
  height: 9px;
  border-radius: 3px;
  flex: none;
  display: block;
}

.saldo-legend strong {
  color: var(--app-text);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

/* La cuenta explicada en una frase. Tres cifras sueltas se leian como si se
   contradijeran ("por pagar 82,50" / "en revision 82,50" / "deuda real 165"). */
.saldo-say {
  margin: .8rem 0 0;
  font-size: .88rem;
  line-height: 1.5;
  color: var(--app-muted);
  text-wrap: pretty;
}

.saldo-say strong {
  color: var(--app-text);
  font-weight: 700;
}

.saldo-cta {
  width: 100%;
  min-height: 50px;
  margin-top: .9rem;
  font-weight: 700;
  font-size: 1rem;
}

.saldo-ok {
  display: flex;
  align-items: center;
  gap: .7rem;
  margin-top: .85rem;
}

.saldo-ok-icon {
  width: 38px;
  height: 38px;
  flex: none;
  border-radius: 999px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.15rem;
  background: color-mix(in srgb, var(--app-success) 12%, white);
  color: var(--app-success);
}

.saldo-ok p {
  margin: 0;
  font-size: .87rem;
  line-height: 1.4;
  color: var(--app-muted);
}

.saldo-ok strong { color: var(--app-text); }

/* ------------------------------------ seguimiento del pago en revision */
/*
 * Va DENTRO de la tarjeta de saldo, separado por un hilo. Es la respuesta a
 * "ya lo vieron?", que es la llamada que el residente hace a la oficina.
 */
.pay-track {
  margin-top: 1rem;
  padding-top: .85rem;
  border-top: 1px solid var(--app-border-soft);
}

.pay-track-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: .6rem;
  font-size: .84rem;
  font-weight: 700;
}

.pay-track-when {
  font-weight: 500;
  font-size: .78rem;
  color: var(--app-muted);
}

.pay-steps {
  display: flex;
  align-items: center;
  margin: .8rem .15rem 0;
}

.pay-step {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: .35rem;
  flex: none;
  width: 76px;
}

.pay-step-dot {
  width: 24px;
  height: 24px;
  border-radius: 999px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: .72rem;
  background: var(--app-border-soft);
  border: 1.5px solid var(--app-border);
  color: var(--app-muted);
}

.pay-step-label {
  font-size: .68rem;
  font-weight: 600;
  line-height: 1.2;
  text-align: center;
  color: var(--app-muted);
}

.pay-step.is-done .pay-step-dot {
  background: var(--app-success);
  border-color: var(--app-success);
  color: #fff;
}

.pay-step.is-done .pay-step-label { color: var(--app-text); }

.pay-step.is-now .pay-step-dot {
  background: var(--app-warning);
  border-color: var(--app-warning);
  color: #fff;
  animation: payPulse 2.2s cubic-bezier(.22, 1, .36, 1) infinite;
}

.pay-step.is-now .pay-step-label {
  color: var(--app-warning);
  font-weight: 700;
}

@keyframes payPulse {
  0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--app-warning) 45%, transparent); }
  55%      { box-shadow: 0 0 0 7px transparent; }
}

.pay-step-line {
  flex: 1;
  height: 2px;
  margin-bottom: 20px;
  border-radius: 2px;
  background: var(--app-border);
}

.pay-step-line.is-done { background: var(--app-success); }

.pay-track-note {
  margin: .7rem 0 0;
  font-size: .78rem;
  line-height: 1.45;
  color: var(--app-muted);
}

/* ------------------------------------------------------ accesos rapidos */
/*
 * Los accesos son una LISTA, no tres fichas en fila.
 *
 * Historia de esto, porque explica el resultado: primero fueron tres botones
 * pildora de 31px (por debajo del minimo de 44 que fija PRODUCT.md), luego tres
 * bloques cuadrados sueltos -que quedaban flotando, al ser lo unico de la
 * pantalla sin contenedor propio- y ahora son filas `.row-item` dentro de una
 * `.section-card`, exactamente la misma pieza que usan los movimientos, los
 * tickets y las unidades.
 *
 * Que sean la misma pieza es lo que hace que la tarjeta de accesos y la de
 * unidades salgan simetricas en la columna izquierda: mismo alto de fila,
 * mismo chip de icono, mismos margenes. No hay CSS propio de "accesos".
 */

/* La flecha de "esto lleva a algun sitio". */
.row-go {
  color: var(--app-muted);
  font-size: .9rem;
  line-height: 1;
}

/* Sin cabecera, la primera fila no necesita su linea de arriba. */
.section-card > .row-item:first-child { border-top: 0; }

/* ------------------------------------------------- fila de lista (.row-*) */
/*
 * El esqueleto de fila del portal del residente: chip de icono tenido por
 * estado + cuerpo (titulo y una linea de contexto) + bloque a la derecha.
 * Nacio para los movimientos del panel y lo usan tambien los tickets; si
 * aparece una tercera lista, se reutiliza esta, no se copia.
 *
 * `--row-tint` lo eligen los modificadores .row-ok / .row-wait / .row-bad /
 * .row-mute, y lo consumen el chip y el texto de estado.
 *
 * Por que existe: cada pago mostraba cuatro campos fijos (fecha, referencia,
 * metodo y un "tipo" que repetia la linea de arriba), ~150px por fila. Aqui
 * son 62 y el resto se abre al tocar.
 */
.row-item {
  display: flex;
  align-items: center;
  gap: .75rem;
  width: 100%;
  min-height: 62px;
  padding: .7rem .9rem;
  border: 0;
  border-top: 1px solid var(--app-border-soft);
  background: none;
  text-align: left;
  color: inherit;
  /* Las filas que son <a> heredaban el subrayado de Bootstrap, asi que el
     titulo y su linea de contexto salian subrayados y las filas que son
     <button> no: la misma lista con dos aspectos. El destino ya lo dice el
     chevron de la derecha. */
  text-decoration: none;
  transition: background-color .15s ease;
}

/* El realce al pasar por encima solo donde de verdad se puede tocar: la lista
   de unidades usa la misma fila y no lleva a ninguna parte, asi que iluminarla
   prometeria un clic que no existe. */
button.row-item,
a.row-item { cursor: pointer; }

button.row-item:hover,
a.row-item:hover { background: var(--app-surface-soft); }

.row-item[aria-expanded="true"] { background: var(--app-surface-soft); }

.row-icon {
  width: 34px;
  height: 34px;
  flex: none;
  border-radius: 10px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: .95rem;
  background: color-mix(in srgb, var(--row-tint) 11%, white);
  color: var(--row-tint);
}

.row-ok { --row-tint: var(--app-success); }
.row-wait  { --row-tint: var(--app-warning); }
.row-bad { --row-tint: var(--app-danger); }
.row-mute  { --row-tint: var(--app-muted); }
.row-primary { --row-tint: var(--app-primary); }

.row-body {
  flex: 1;
  min-width: 0;
}

.row-title {
  display: block;
  font-size: .89rem;
  font-weight: 700;
  line-height: 1.25;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.row-meta {
  display: block;
  margin-top: .1rem;
  font-size: .76rem;
  color: var(--app-muted);
}

.row-right {
  flex: none;
  text-align: right;
}

.mv-amount {
  display: block;
  font-size: .95rem;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
}

.mv-state {
  display: block;
  margin-top: .1rem;
  font-size: .7rem;
  font-weight: 700;
  color: var(--row-tint);
}

/* Detalle: referencia, metodo y edificio. Se pide, no se impone. */
.row-detail {
  padding: 0 .9rem .8rem 3.6rem;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
  gap: .5rem .9rem;
}

.row-detail-label {
  display: block;
  font-size: .68rem;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--app-muted);
}

.row-detail-value {
  display: block;
  margin-top: .1rem;
  font-size: .84rem;
  font-weight: 600;
  overflow-wrap: anywhere;
}

/* El motivo del rechazo NO se pliega: es lo unico accionable de la fila. */
.mv-why {
  margin: 0;
  padding: 0 .9rem .75rem 3.6rem;
  font-size: .78rem;
  line-height: 1.45;
  color: var(--app-danger);
}

/*
 * El mismo motivo, pero DENTRO de la fila. En la hoja de movimientos las filas
 * se filtran, y un parrafo hermano se habria quedado en pantalla despues de
 * ocultar su fila.
 */
.row-why {
  display: block;
  margin-top: .25rem;
  font-size: .76rem;
  line-height: 1.45;
  font-weight: 600;
  color: var(--app-danger);
}

/* --------------------------------------------------------------- avisos */
/* La fecha en bloque a la izquierda: un aviso se ubica antes por CUANDO es que
   por como se titula, y asi la columna de fechas se escanea de un vistazo. */
.notice-block {
  display: flex;
  align-items: flex-start;
  gap: .8rem;
  padding: .9rem;
}

.notice-block + .notice-block {
  border-top: 1px solid var(--app-border-soft);
}

.notice-when {
  flex: none;
  width: 64px;
  padding: .5rem .35rem;
  border-radius: 10px;
  border: 1px solid color-mix(in srgb, var(--app-primary) 20%, white);
  background: color-mix(in srgb, var(--app-primary) 7%, white);
  text-align: center;
  line-height: 1.1;
}

.notice-day {
  display: block;
  font-size: 1.3rem;
  font-weight: 800;
  color: var(--app-primary);
  font-variant-numeric: tabular-nums;
}

.notice-stamp {
  display: block;
  margin-top: .15rem;
  font-size: .62rem;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: .05em;
  color: var(--app-muted);
}

.notice-said { min-width: 0; }

.notice-heading {
  margin: .3rem 0 .25rem;
  font-size: .92rem;
  font-weight: 700;
}

.notice-text {
  margin: 0;
  font-size: .85rem;
  line-height: 1.5;
  color: var(--app-muted);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  text-wrap: pretty;
}

/* Tres avisos completos empujaban el resto fuera de pantalla. */
.notice-more {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  min-height: 46px;
  padding: .75rem .9rem;
  border: 0;
  border-top: 1px solid var(--app-border-soft);
  border-radius: 0 0 var(--card-radius) var(--card-radius);
  background: var(--app-surface-soft);
  font-size: .82rem;
  font-weight: 700;
  color: var(--app-primary);
}

/* ------------------------------- aviso de "cambia la clave que te dieron" */
.pwd-notice-icon {
  width: 54px;
  height: 54px;
  margin: 0 auto .85rem;
  border-radius: 999px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.5rem;
  background: color-mix(in srgb, var(--app-warning) 12%, white);
  color: var(--app-warning);
}

.pwd-notice-why {
  margin: .9rem 0 0;
  padding: .8rem .9rem;
  border-radius: 10px;
  border: 1px solid color-mix(in srgb, var(--app-warning) 26%, white);
  background: color-mix(in srgb, var(--app-warning) 6%, white);
  font-size: .84rem;
  line-height: 1.5;
  color: var(--app-text);
}

.pwd-notice-why ul {
  margin: .5rem 0 0;
  padding-left: 1.1rem;
  color: var(--app-muted);
}

.pwd-notice-why li + li { margin-top: .25rem; }

/* ------------------------------------------------- reparto en escritorio */
/*
 * Primera version: columna estrecha con TODO y una columna ancha con un solo
 * bloque. Se leia como el movil estirado, con medio lienzo vacio.
 *
 * Ahora las dos columnas llevan carga: a la izquierda lo que el residente
 * decide (saldo, accesos, sus unidades) y a la derecha lo que lee (sus
 * movimientos y los avisos del edificio). El saludo pasa a `.app-hero`, que es
 * la cabecera que ya usan las demas pantallas del sistema; el portal del
 * residente era el unico sitio donde el titulo flotaba suelto sobre el fondo.
 */
/*
 * Dos columnas de verdad, no areas de una rejilla.
 *
 * Con `grid-template-areas` la tarjeta de movimientos ocupaba dos filas, asi
 * que SU altura estiraba las filas del saldo y de los accesos; los accesos
 * quedaban clavados arriba de una fila sobredimensionada, con un hueco enorme
 * debajo, y parecian flotar. En una rejilla, las filas alinean las dos
 * columnas: cualquier bloque mas alto que su vecino reparte el sobrante.
 *
 * Con una columna por lado cada una apila lo suyo con el mismo hueco y ninguna
 * arrastra a la otra.
 */
/*
 * En el telefono las columnas se disuelven (`display: contents`) y las seis
 * piezas caen en una sola columna. El orden de lectura NO es el de las
 * columnas: "Mis unidades" es dato de referencia y se va al final, para que no
 * cueste la primera pantalla.
 */
.res-col { display: contents; }

.res-area-hero     { order: 1; }
.res-area-saldo    { order: 2; }
.res-area-quick    { order: 3; }
.res-area-movs     { order: 4; }
.res-area-avisos   { order: 5; }
.res-area-unidades { order: 6; }

@media (min-width: 992px) {
  .res-page {
    display: flex;
    flex-wrap: wrap;
    /* stretch (y no flex-start): iguala el alto de las dos columnas. Ver la
       nota de .res-area-unidades al final del bloque. */
    align-items: stretch;
    gap: 1rem;
  }

  .res-area-hero { flex: 1 0 100%; }

  /* La cabecera va primero (order 1); las dos columnas despues, en el orden en
     que estan escritas. Dentro de cada una manda el `order` de arriba. */
  .res-col {
    order: 2;
    display: flex;
    flex-direction: column;
    gap: 1rem;
    min-width: 0;
  }

  .res-col-main { flex: 0 1 400px; }
  .res-col-side { flex: 1 1 0; }

  /*
   * Las dos columnas cierran a la misma altura.
   *
   * `align-items: stretch` iguala el alto de las COLUMNAS, no el de las
   * tarjetas: sin esto la columna corta terminaba antes y quedaba un escalon
   * al pie. La ultima tarjeta de cada columna se lleva el sobrante, que en la
   * izquierda es "Mi unidad" y en la derecha "Del edificio". Solo crece una
   * por columna, asi que el reparto no es ambiguo.
   */
  .res-area-unidades,
  .res-area-avisos { flex: 1 1 auto; }
}

@media (prefers-reduced-motion: reduce) {
  .pay-step.is-now .pay-step-dot { animation: none; }
  .row-item,
  .filtro-chip,
  .tk-summary-item { transition: none; }
}

/* --------------------------------------------- buscador de una lista (.buscador) */
/*
 * Una fila de busqueda para las listas del portal: la de reclamos y la de
 * movimientos. Cada pantalla decide en que texto busca; lo que se comparte es
 * la forma y el comportamiento (sin acentos ni mayusculas, la X para vaciar).
 *
 * No es el buscador global del navbar (.global-search), que va contra el
 * sistema entero: este solo filtra lo que ya esta en pantalla.
 */
.buscador {
  display: flex;
  align-items: center;
  gap: .5rem;
  padding: .55rem .9rem;
  border-top: 1px solid var(--app-border-soft);
  color: var(--app-muted);
  /* Dentro de un modal (que es una columna flex) la barra no cede altura: el
     que se encoge es el cuerpo, que para eso lleva su propio scroll. */
  flex: 0 0 auto;
}

.buscador > i { font-size: .95rem; flex: none; }

.buscador-input {
  flex: 1;
  min-width: 0;
  min-height: 34px;
  border: 0;
  background: none;
  color: var(--app-text);
  font-size: .9rem;
  font-weight: 600;
}

.buscador-input::placeholder {
  color: var(--app-muted);
  font-weight: 500;
}

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

/* El foco se ve en la fila entera, que es lo que el ojo busca. */
.buscador:focus-within {
  color: var(--app-primary);
  background: color-mix(in srgb, var(--app-primary) 5%, white);
}

/* La X nativa de <input type="search"> no aparece en todos los navegadores. */
.buscador-input::-webkit-search-cancel-button { display: none; }

.buscador-x {
  flex: none;
  width: 30px;
  height: 30px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 0;
  border-radius: 999px;
  background: none;
  color: var(--app-muted);
  font-size: .8rem;
  cursor: pointer;
}

.buscador-x:hover { background: var(--app-surface-soft); color: var(--app-text); }

/*
 * Filtrar oculta filas, y `.row-item` es flex, asi que el atributo `hidden`
 * por si solo no las quita de la pantalla.
 */
.row-item[hidden],
.empty-state[hidden],
.buscador-x[hidden] { display: none !important; }

/* ------------------------------------------- filtro por estado (.filtro-chip) */
/*
 * La version compacta del filtro de estado, para cuando no hay sitio para las
 * fichas con cifra grande (la hoja de movimientos). Misma idea: el numero va
 * dentro de la pildora, asi que el filtro y la cuenta son una sola cosa.
 */
.filtro-chips {
  display: flex;
  flex-wrap: wrap;
  gap: .4rem;
  padding: .6rem .9rem;
  border-top: 1px solid var(--app-border-soft);
  flex: 0 0 auto;
}

.filtro-chip {
  display: inline-flex;
  align-items: center;
  gap: .35rem;
  min-height: 36px;
  padding: .3rem .7rem;
  border-radius: 999px;
  border: 1px solid var(--app-border);
  background: var(--app-surface);
  color: var(--app-muted);
  font-size: .8rem;
  font-weight: 700;
  cursor: pointer;
  transition: border-color .15s ease, background-color .15s ease, color .15s ease;
}

.filtro-chip:hover { border-color: color-mix(in srgb, var(--row-tint) 45%, var(--app-border)); }

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

.filtro-chip[aria-pressed="true"] {
  border-color: color-mix(in srgb, var(--row-tint) 55%, white);
  background: color-mix(in srgb, var(--row-tint) 9%, white);
  color: var(--row-tint);
}

/* La cifra en tabular para que las pildoras no bailen al filtrar. */
.filtro-chip-n {
  font-variant-numeric: tabular-nums;
  opacity: .75;
}

/*
 * El rango de fechas comparte la fila con las pildoras de estado y se va a la
 * derecha cuando hay sitio. Es un <select> y no mas pildoras porque son cinco
 * opciones excluyentes y solo una esta puesta a la vez.
 */
.filtro-rango-caja {
  position: relative;
  margin-left: auto;
  display: inline-flex;
  align-items: center;
}

/* El chevron es un icono de la fuente, no una flecha dibujada en un data URI:
   asi hereda el color del token en vez de repetir su valor en hexadecimal. */
.filtro-rango-caja > i {
  position: absolute;
  right: .6rem;
  font-size: .7rem;
  color: var(--app-muted);
  pointer-events: none;
}

.filtro-rango {
  min-height: 36px;
  padding: .3rem 1.9rem .3rem .7rem;
  border-radius: 999px;
  border: 1px solid var(--app-border);
  background: var(--app-surface);
  color: var(--app-text);
  font-size: .8rem;
  font-weight: 700;
  cursor: pointer;
  appearance: none;
}

.filtro-rango:focus-visible {
  outline: 2px solid var(--app-primary);
  outline-offset: 2px;
}

/* Solo se ve con el rango "a medida" puesto. */
.filtro-fechas {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .5rem;
  padding: 0 .9rem .6rem;
  font-size: .8rem;
  font-weight: 700;
  color: var(--app-muted);
}

.filtro-fechas input {
  min-height: 36px;
  padding: .3rem .6rem;
  border: 1px solid var(--app-border);
  border-radius: 10px;
  background: var(--app-surface);
  color: var(--app-text);
  font-size: .8rem;
  font-weight: 600;
}

.filtro-fechas[hidden] { display: none !important; }

/* -------------------------------------------------------------- paginador */
/*
 * Se pagina lo que YA quedo filtrado, nunca al reves: buscar mira la lista
 * entera y luego se corta. Por eso la barra dice de cuantas paginas son los
 * resultados, no de cuantas es el historial.
 */
.paginador {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: .5rem;
  padding: .55rem .9rem;
  border-top: 1px solid var(--app-border);
  background: var(--app-surface-soft);
  flex: 0 0 auto;
}

.paginador[hidden] { display: none !important; }

.paginador-info {
  font-size: .78rem;
  font-weight: 700;
  color: var(--app-muted);
  font-variant-numeric: tabular-nums;
}

.paginador-btn {
  display: inline-flex;
  align-items: center;
  gap: .3rem;
  min-height: 36px;
  padding: .3rem .8rem;
  border: 1px solid var(--app-border);
  border-radius: 999px;
  background: var(--app-surface);
  color: var(--app-text);
  font-size: .8rem;
  font-weight: 700;
  cursor: pointer;
}

.paginador-btn:hover:not(:disabled) { border-color: var(--app-primary); color: var(--app-primary); }

.paginador-btn:disabled {
  cursor: default;
  opacity: .45;
}

/* ==========================================================================
   TICKETS DEL RESIDENTE
   --------------------------------------------------------------------------
   Antes: un bloque <style> de 125 lineas en app/Views/user/tickets.php con su
   propio :root, diez hexadecimales sueltos (incluido un indigo #6366f1 que no
   es el azul de marca) y la lista impresa dos veces (tabla de escritorio +
   .mobile-ticket-list).

   El cambio de fondo es de ORDEN: en el telefono la columna del formulario iba
   primero, asi que quien entraba a ver en que iba su reclamo se encontraba un
   formulario en blanco de cinco campos y sus tickets debajo de todo. Ahora
   manda la lista y crear uno nuevo se pide.

   Las filas usan el esqueleto .row-* de la seccion anterior.
   ========================================================================== */

.tk-page {
  display: grid;
  gap: .85rem;
}

/* La cabecera es `.app-hero`; aqui solo el reparto del boton dentro de ella. */
.tk-hero {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: .9rem;
}

/* ------------------------------------------------------------- resumen */
/* Tres cuentas reales del propio listado. No sale si no hay tickets: tres
   ceros alineados no informan de nada y solo llenan hueco. */
/* ---------------------------------------------- resumen = filtro de estado */
/*
 * Las cuentas por estado son el filtro. Ponerlas a mirar y anadir debajo una
 * fila de pildoras con las mismas palabras habria dejado los mismos cuatro
 * estados dos veces en la misma pantalla; aqui la cifra que ya estaba se toca.
 *
 * Cuatro celdas (Todos + los tres estados): 2x2 en el telefono y una columna
 * de pie en escritorio, donde esta tira vive en la barra lateral.
 */
.tk-summary {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: .5rem;
}

.tk-summary-item {
  display: flex;
  align-items: center;
  gap: .6rem;
  width: 100%;
  min-height: 60px;
  padding: .7rem .75rem;
  border-radius: var(--card-radius);
  border: 1px solid var(--app-border);
  background: var(--app-surface);
  box-shadow: var(--app-shadow-sm);
  text-align: left;
  cursor: pointer;
  transition: border-color .15s ease, background-color .15s ease;
}

.tk-summary-item:hover { border-color: color-mix(in srgb, var(--row-tint) 45%, var(--app-border)); }

.tk-summary-item:focus-visible {
  outline: 2px solid var(--app-primary);
  outline-offset: 2px;
}

/* El estado activo se tine del color del propio estado, que es el mismo que
   lleva su chip en la lista de abajo. */
.tk-summary-item[aria-pressed="true"] {
  border-color: color-mix(in srgb, var(--row-tint) 55%, white);
  background: color-mix(in srgb, var(--row-tint) 8%, white);
}

.tk-summary-item[aria-pressed="true"] .tk-summary-label { color: var(--row-tint); }

/* Estado sin ningun reclamo: se sigue leyendo la cifra, pero no se pulsa. */
.tk-summary-item:disabled {
  cursor: default;
  opacity: .55;
}

.tk-summary-item:disabled:hover { border-color: var(--app-border); }

.tk-summary-value {
  display: block;
  font-size: 1.35rem;
  font-weight: 800;
  line-height: 1.05;
  font-variant-numeric: tabular-nums;
  color: var(--row-tint);
}

.tk-summary-label {
  display: block;
  font-size: .72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--app-muted);
}

/* Crear un ticket es la accion, no el contenido: un boton, y el formulario
   llega como hoja (app.css ya convierte cualquier modal en hoja a <=767.98px). */
.tk-new-btn {
  width: 100%;
  min-height: 50px;
  font-weight: 700;
  font-size: 1rem;
}

/* --------------------------------------------------------- chip de estado */
.row-chip {
  display: inline-flex;
  align-items: center;
  white-space: nowrap;
  padding: .2rem .55rem;
  border-radius: 999px;
  font-size: .7rem;
  font-weight: 700;
  background: color-mix(in srgb, var(--row-tint) 12%, white);
  border: 1px solid color-mix(in srgb, var(--row-tint) 30%, white);
  color: var(--row-tint);
}

/* ------------------------------------------------------------- prioridad */
/*
 * La prioridad tenia color (un punto de 8px) solo en la lista, y en el
 * selector no: con "Media" marcada el boton salia gris carbon, igual que
 * saldria "Alta". Aqui el color es el mismo en los dos sitios.
 */
.tk-prio {
  display: inline-flex;
  align-items: center;
  gap: .3rem;
  white-space: nowrap;
}

.tk-prio::before {
  content: "";
  width: 8px;
  height: 8px;
  border-radius: 999px;
  flex: none;
  background: var(--tk-prio-color, var(--app-muted));
}

.tk-prio-alta  { --tk-prio-color: var(--app-danger); }
.tk-prio-media { --tk-prio-color: var(--app-warning); }
.tk-prio-baja  { --tk-prio-color: var(--app-info); }

/* Selector de prioridad: tres objetivos de 46px que se tinen del color real.
   Los radios se sacan del flujo para que las tres celdas de la rejilla sean
   las etiquetas; `position: relative` aqui es lo que los mantiene dentro. */
.tk-prio-pick {
  position: relative;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: .4rem;
}

.tk-prio-pick input {
  position: absolute;
  top: 0;
  left: 0;
  opacity: 0;
  pointer-events: none;
}

.tk-prio-pick label {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: .35rem;
  min-height: 46px;
  padding: .4rem .5rem;
  border-radius: 10px;
  border: 1px solid var(--app-border);
  background: var(--app-surface);
  color: var(--app-muted);
  font-size: .85rem;
  font-weight: 600;
  cursor: pointer;
  transition: border-color .15s ease, background-color .15s ease, color .15s ease;
}

.tk-prio-pick label::before {
  content: "";
  width: 9px;
  height: 9px;
  border-radius: 999px;
  flex: none;
  background: var(--tk-prio-color);
  opacity: .45;
  transition: opacity .15s ease;
}

.tk-prio-pick input:checked + label {
  border-color: var(--tk-prio-color);
  background: color-mix(in srgb, var(--tk-prio-color) 9%, white);
  color: var(--tk-prio-color);
  font-weight: 700;
}

.tk-prio-pick input:checked + label::before { opacity: 1; }

.tk-prio-pick input:focus-visible + label {
  outline: 2px solid var(--app-primary);
  outline-offset: 2px;
}

/* ------------------------------------------ aviso de respuestas del admin */
.tk-answers {
  display: inline-flex;
  align-items: center;
  gap: .35rem;
  margin-top: .3rem;
  padding: .2rem .5rem;
  border-radius: 8px;
  background: color-mix(in srgb, var(--app-primary) 9%, white);
  color: var(--app-primary);
  font-size: .72rem;
  font-weight: 700;
}

/* --------------------------------------------------------- conversacion */
/*
 * Mismo lenguaje que la bandeja del administrador: lo que el residente ve
 * aqui es el otro lado del mismo hilo.
 */
.tk-thread {
  display: flex;
  flex-direction: column;
  gap: .75rem;
  max-height: 46vh;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: .25rem .15rem .5rem;
}

.tk-msg { max-width: 86%; }

.tk-msg--admin { align-self: flex-end; }

.tk-msg-meta {
  font-size: .68rem;
  color: var(--app-muted);
  font-weight: 700;
  margin-bottom: .2rem;
}

.tk-msg--admin .tk-msg-meta { text-align: right; }

.tk-bubble {
  border: 1px solid var(--app-border);
  border-radius: 14px;
  border-bottom-left-radius: 4px;
  padding: .6rem .8rem;
  font-size: .86rem;
  line-height: 1.55;
  white-space: pre-wrap;
  word-break: break-word;
  color: var(--app-text);
  background: var(--app-surface-soft);
}

.tk-msg--admin .tk-bubble {
  background: color-mix(in srgb, var(--app-primary) 8%, white);
  border-color: color-mix(in srgb, var(--app-primary) 22%, var(--app-border));
  border-bottom-left-radius: 14px;
  border-bottom-right-radius: 4px;
}

.tk-attach {
  display: inline-flex;
  align-items: center;
  gap: .35rem;
  margin-top: .45rem;
  padding: .3rem .55rem;
  border: 1px solid var(--app-border);
  border-radius: 8px;
  background: var(--app-surface);
  color: var(--app-primary);
  font-size: .76rem;
  font-weight: 700;
  text-decoration: none;
}

.tk-attach:hover { border-color: var(--app-primary); }

.tk-shot {
  display: block;
  margin-top: .45rem;
  max-width: 100%;
  max-height: 220px;
  border-radius: 10px;
  border: 1px solid var(--app-border);
}

.tk-composer {
  border-top: 1px solid var(--app-border);
  padding-top: .85rem;
  margin-top: .25rem;
}

/* ------------------------------------------------------------- adjunto */
/*
 * El input de archivo nativo ("Seleccionar archivo / Ningun archivo
 * seleccionado") era el control menos tactil del formulario, y el modal de
 * respuesta ya tenia este otro para lo mismo: dos vocabularios en la misma
 * pantalla. Ahora los dos usan este.
 */
.tk-file-label {
  display: flex;
  align-items: center;
  gap: .5rem;
  width: 100%;
  min-height: 46px;
  padding: .5rem .75rem;
  border: 1px dashed var(--app-border);
  border-radius: 10px;
  background: var(--app-surface);
  color: var(--app-muted);
  font-size: .85rem;
  font-weight: 600;
  cursor: pointer;
  transition: border-color .15s ease, color .15s ease;
}

.tk-file-label:hover {
  color: var(--app-primary);
  border-color: var(--app-primary);
}

.tk-file-label i { font-size: 1rem; }

.tk-file-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* --------------------------------------------------- reparto en escritorio */
/*
 * El formulario vive en UN solo sitio: la hoja. Tenerlo ademas fijo en una
 * columna de escritorio obligaba a imprimirlo dos veces -que es la duplicacion
 * que esta pasada elimina- o a mover nodos en caliente, que en este proyecto
 * no se hace. Asi que en las dos anchuras se abre igual, y lo unico que cambia
 * es que en pantalla ancha la lista no se estira a 1320px.
 */
@media (min-width: 992px) {
  .tk-page {
    grid-template-columns: minmax(0, 1fr) minmax(0, 320px);
    grid-template-areas:
      "hero    hero"
      "lista   resumen";
    /* stretch (y no start): la lista y la columna de estados cierran a la
       misma altura, como las dos columnas del panel. */
    align-items: stretch;
    gap: 1rem;
  }

  .tk-area-hero    { grid-area: hero; }
  .tk-area-lista   { grid-area: lista; }
  .tk-area-resumen { grid-area: resumen; }

  /*
   * En el telefono el resumen es una tira de dos por dos; en escritorio se
   * pone de pie en la columna lateral, que es donde hay sitio de sobra.
   *
   * `align-content: start` para que las cuatro fichas conserven su alto: con
   * la rejilla estirada, el reparto por defecto las habria inflado hasta el
   * doble para llenar la columna. El sobrante lo absorbe la tarjeta de la
   * lista, que para eso es un contenedor.
   */
  .tk-summary {
    grid-template-columns: 1fr;
    align-content: start;
  }

  /* El boton se sienta a la derecha del titulo dentro de la cabecera. */
  .tk-hero {
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
  }

  .tk-new-btn {
    width: auto;
    flex: none;
    padding-inline: 1.5rem;
  }
}
