@media (max-width: 767.98px) {
  table[data-cards],
  table[data-cards] tbody,
  table[data-cards] thead,
  table[data-cards] tfoot,
  table[data-cards] tr,
  table[data-cards] td {
    display: block;
    width: 100%;
    box-sizing: border-box;
  }

  table[data-cards] {
    border-collapse: collapse;
  }

  table[data-cards] thead {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
  }

  table[data-cards] tbody tr {
    margin-bottom: 12px;
    border: 1px solid var(--border-color);
    border-radius: 8px;
    background: var(--bg-primary);
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
    overflow: hidden;
  }

  table[data-cards] tbody tr:last-child {
    margin-bottom: 0;
  }

  table[data-cards] td {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 10px 14px;
    border-bottom: 1px solid var(--border-color);
    text-align: right;
  }

  table[data-cards] tbody tr:last-child td:last-child {
    border-bottom: none;
  }

  table[data-cards] td::before {
    content: attr(data-label);
    flex: 0 0 auto;
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.4px;
    color: var(--text-secondary);
    text-align: left;
  }

  table[data-cards] td:empty {
    display: none;
  }

  table[data-cards] td:last-child {
    justify-content: flex-end;
  }
}

/* ---------------------------------------------------------------------------
   Celdas con texto largo.

   Un titulo de ticket admite 200 caracteres y un payload de auditoria es un
   JSON entero; puestos crudos en una celda estiran la fila y descuadran la
   tabla. Medido: en Auditoria una fila con payload media 115px contra 79px de
   una normal, y en 375px la diferencia llegaba a 225px.

   Recortar a dos lineas mantiene la grilla pareja siempre. El valor completo no
   se pierde: queda en el title para una lectura rapida, y lo que necesita
   inspeccionarse —el payload— se abre en un visor. Es el mismo recurso que ya
   usa .ticket-description en tickets.css.
   --------------------------------------------------------------------------- */
.cell-clamp {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  overflow-wrap: anywhere;
}

/* ---------------------------------------------------------------------------
   Pie de pagina pegado al fondo.

   El footer se inyecta como ultimo hijo del body, asi que con poco contenido
   quedaba flotando a media pantalla: en Gestionar Clientes sin clientes sobraban
   502px de vacio debajo suyo en 1280x800. header.js ya lo empujaba, pero solo en
   las paginas cuyo body es flex en fila (las de auth.css); el resto, que son
   block, no tenian nada que lo bajara.

   Se usa `position: sticky; top: 100vh` en lugar de poner el body en columna
   flex. Un body flex rompe estas vistas: sus contenedores centran con
   `margin: 20px auto`, y un margen auto en el eje transversal ANULA
   `align-items: stretch` y deja al item en shrink-to-fit — medido, el contenedor
   de Gestionar Clientes pasaba de 1200px a 461px. Es el mismo desborde que
   13adf3b tuvo que corregir. Sticky deja el flujo en block, asi que ningun hijo
   cambia de tamano: con contenido corto el pie baja al fondo del viewport y con
   contenido largo queda al final del scroll, que es el otro caso a respetar.

   box-sizing se declara porque no todas las vistas cargan una hoja con la regla
   `*`: sin el, el padding-top de 58px de la topbar se sumaria al 100vh y dejaria
   58px de scroll permanente en cada pagina.
   --------------------------------------------------------------------------- */
html {
  min-height: 100%;
}

body {
  box-sizing: border-box;
  min-height: 100vh;
  min-height: 100dvh;
}

/* Mas especifico que la regla que inyecta header.js, que gana por orden al
   insertarse en el head despues de esta hoja. */
body > .global-footer {
  position: sticky;
  top: 100vh;
}

/* ---------------------------------------------------------------------------
   Caja de los modales.

   Las paginas que usan .modal/.modal-content centraban el dialogo con
   `margin: 15% auto`, y un margen porcentual se resuelve contra el ANCHO del
   bloque contenedor, no contra el alto: 216px de margen superior en un viewport
   de 1440px y 192px en uno de 1280px. Cuanto mas ancha la pantalla, mas abajo
   arrancaba el dialogo. Encima el overlay es `position: fixed; height: 100%` sin
   overflow, asi que lo que ese margen empujaba fuera de pantalla no se podia
   alcanzar de ninguna forma: en 1440x900 el boton de submit quedaba entero por
   debajo del viewport y nada scrolleaba.

   Ahora el dialogo es una columna flex acotada al viewport: la cabecera y el pie
   quedan fijos y el cuerpo scrollea, asi ningun campo puede quedar cortado en
   ningun tamano. El tope se expresa en dvh para descontar la barra del navegador
   en telefonos, con vh como respaldo para motores que no lo soporten.
   --------------------------------------------------------------------------- */
.modal-content {
  display: flex;
  flex-direction: column;
  margin: 16px auto;
  max-height: calc(100vh - 32px);
  max-height: calc(100dvh - 32px);
  /* Respaldo para los dialogos que no tienen cuerpo propio, como el de
     contrasena temporal: scrollea la caja entera en lugar de recortarse. */
  overflow-y: auto;
}

.modal-content > .modal-header,
.modal-content > .modal-footer {
  flex: 0 0 auto;
}

/* El cuerpo del modal. `min-height: 0` no es opcional: un item flex arranca en
   `min-height: auto` y se niega a achicarse por debajo de su contenido, lo que
   anula el overflow-y sin dar ningun sintoma. */
.modal-content > .modal-body,
.modal-content > form {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  /* El submit pegado abajo tapa el ultimo campo cuando el foco o scrollIntoView
     lo traen a la vista. Este colchon reserva su alto al calcular ese scroll,
     asi ningun campo queda debajo del boton. */
  scroll-padding-bottom: 72px;
}

/* Los formularios de estas vistas no tienen un pie aparte: el submit es el
   ultimo hijo del form. Pegarlo abajo del area scrolleable deja la accion
   principal siempre visible sin tener que envolver el boton en cada modal. */
.modal-content > form > button[type="submit"] {
  position: sticky;
  bottom: 0;
  /* Franja del color del dialogo alrededor del boton: los campos pasan por
     detras en lugar de asomar pegados a su borde. */
  box-shadow: 0 0 0 8px var(--bg-primary);
}

@media (max-width: 767.98px) {
  .modal {
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
  }

  .modal-content {
    margin: 12px auto;
    width: calc(100% - 24px);
    max-width: 500px;
    max-height: calc(100vh - 24px);
    max-height: calc(100dvh - 24px);
  }

  .au-modal-overlay {
    padding: 12px;
    padding-left: max(12px, env(safe-area-inset-left));
    padding-right: max(12px, env(safe-area-inset-right));
  }

  .au-modal {
    max-height: calc(100dvh - 24px);
    overflow-y: auto;
  }
}

.page-actions {
  flex-wrap: wrap;
  justify-content: flex-start;
}

.toast-container {
  left: max(var(--spacing-lg), env(safe-area-inset-left));
  right: max(var(--spacing-lg), env(safe-area-inset-right));
}

@media (max-width: 767.98px) {
  .toast {
    min-width: 0;
    max-width: none;
    width: 100%;
  }
}

@media (pointer: coarse) {
  .btn-small {
    min-height: 44px;
    padding: 10px 16px;
  }

  .notification-item,
  .dropdown-item {
    min-height: 44px;
  }

  .toast__close {
    width: 32px;
    height: 32px;
  }
}
