/**
 * Overlays: modal, drawer y toasts.
 *
 * Modal y drawer se construyen sobre <dialog> nativo, que aporta gratis el
 * top-layer, el foco atrapado, el cierre con Escape y el backdrop.
 */

/* ==================== Base común <dialog> ==================== */

.ds-modal,
.ds-drawer {
  /*
   * Posición y centrado explícitos, sin depender de la hoja del navegador.
   *
   * Un `<dialog>` abierto con `showModal()` recibe `position: fixed`, `inset: 0`
   * y `margin: auto` de la hoja del propio navegador, y eso cambió entre
   * versiones: hubo Safari y WebView de Android que lo dejaban en `absolute`
   * dentro de un contenedor con scroll bloqueado, con el resultado de un backdrop
   * visible y la caja fuera de la pantalla. Declararlo acá cuesta cuatro líneas y
   * saca la variable del medio.
   */
  position: fixed;
  inset: 0;
  padding: 0;
  border: none;
  background: var(--ds-surface);
  color: var(--ds-text);
  box-shadow: var(--ds-shadow-lg);
  /* `dvh` existe en Safari desde 15.4. Sin el respaldo en `vh`, un navegador
     viejo descarta la declaración y el overlay queda sin tope de alto. */
  max-height: 100vh;
  max-height: 100dvh;
}

/* Un diálogo cerrado no se muestra, incluso si algo pisó el `display`. */
.ds-modal:not([open]),
.ds-drawer:not([open]) {
  display: none;
}

.ds-modal::backdrop,
.ds-drawer::backdrop {
  background: var(--ds-overlay);
  animation: ds-fade-in var(--ds-duration-base) var(--ds-ease);
}

@keyframes ds-fade-in {
  from { opacity: 0; }
}

/* ==================== Modal ==================== */

.ds-modal {
  width: min(560px, calc(100vw - 2 * var(--ds-space-4)));
  /*
   * Sin `height`: el alto lo da el contenido, acotado por `max-height`.
   *
   * Acá hubo un `height: fit-content` y en Safari aplastaba el modal contra el
   * borde inferior. El motivo es una diferencia real entre motores: al calcular
   * el alto intrínseco de un contenedor, WebKit resuelve el porcentaje de un hijo
   * —el `height: 100%` del interior— como **cero**, mientras Chrome y Firefox lo
   * tratan como automático. Con `height: auto` no se entra en ese camino.
   */
  max-height: min(85vh, 800px);
  max-height: min(85dvh, 800px);
  border-radius: var(--ds-radius-xl);
  margin: auto;
  animation: ds-modal-in var(--ds-duration-base) var(--ds-ease);
}

@keyframes ds-modal-in {
  from { opacity: 0; transform: translateY(8px) scale(0.98); }
}

.ds-modal--sm { width: min(400px, calc(100vw - 2 * var(--ds-space-4))); }
.ds-modal--lg { width: min(800px, calc(100vw - 2 * var(--ds-space-4))); }

/* ==================== Drawer ==================== */

.ds-drawer {
  width: min(440px, 100vw);
  height: 100vh;
  height: 100dvh;
  max-height: 100vh;
  max-height: 100dvh;
  margin: 0 0 0 auto;
  border-radius: 0;
  animation: ds-drawer-in var(--ds-duration-base) var(--ds-ease);
}

@keyframes ds-drawer-in {
  from { transform: translateX(100%); }
}

.ds-drawer--left {
  margin: 0 auto 0 0;
  animation-name: ds-drawer-in-left;
}

@keyframes ds-drawer-in-left {
  from { transform: translateX(-100%); }
}

.ds-drawer--wide { width: min(680px, 100vw); }

/* ==================== Presentación móvil ====================
 *
 * Debajo de 768px los overlays dejan de ser una caja flotante sobre una página
 * y pasan a ser lo que la plataforma usa en un teléfono. `data-mobile` lo decide
 * y lo escribe `overlay.js`:
 *
 *   sheet      → hoja que sube desde abajo. El destino por defecto del panel
 *                lateral y del modal: es donde llega el pulgar, y con el teclado
 *                abierto no queda partido al medio como el diálogo centrado.
 *   fullscreen → pantalla completa. Para formularios largos y para lo que en
 *                escritorio era un modal grande: en 360px de ancho, una hoja a
 *                media altura con doce campos es peor que una pantalla propia.
 *   dialog     → se queda centrado. Salida de emergencia para lo que de verdad
 *                tiene que interrumpir.
 *
 * El atributo gana en especificidad (0,2,0) sobre `--sm` y `--lg` (0,1,0), así
 * que el ancho de la variante no compite con el de la hoja.
 */

@media (max-width: 767.98px) {
  .ds-modal[data-mobile="sheet"],
  .ds-drawer[data-mobile="sheet"] {
    /*
     * El borde de arriba queda suelto y el de abajo pegado. Es la única forma
     * que no depende de cómo cada motor resuelva un caso ambiguo: con `inset: 0`
     * —arriba y abajo fijos— y `height: auto`, la regla del posicionamiento dice
     * que el alto se resuelve **primero** y los márgenes automáticos se ponen en
     * cero, así que la hoja se pegaba arriba. Con `top: auto` no hay nada que
     * resolver: el alto lo da el contenido y el pie se apoya en el borde
     * inferior de la pantalla.
     */
    inset: auto 0 0 0;
    width: 100vw;
    max-width: 100vw;
    height: auto;
    /* 92% deja ver una franja del fondo: es lo que dice "esto está encima de
       algo", y el borde de la pantalla deja de ser el único modo de salir. */
    max-height: 92vh;
    max-height: 92dvh;
    margin: 0;
    border-radius: var(--ds-radius-xl) var(--ds-radius-xl) 0 0;
    animation-name: ds-sheet-in;
  }

  /*
   * Hoja alta: llega al tope aunque el contenido sea corto.
   *
   * Para los formularios cuyo control abre algo hacia abajo —un select propio,
   * un autocompletado, un calendario—. Esa lista se dibuja **dentro** del cuerpo
   * del overlay, que tiene scroll propio, así que lo que sobresale se corta: en
   * una hoja de tres dedos de alto, un desplegable de 256px se ve por la mitad.
   * Subir la hoja no arregla el recorte, le da lugar a la lista para caber.
   */
  .ds-modal[data-mobile="sheet"][data-mobile-height="tall"],
  .ds-drawer[data-mobile="sheet"][data-mobile-height="tall"] {
    height: 92vh;
    height: 92dvh;
  }

  /*
   * Y lo hace sola cuando el contenido **se anuncia** como algo que despliega.
   *
   * `aria-haspopup` y `role="combobox"` no son una clase del proyecto: son lo
   * que cualquier control con lista propia ya declara para los lectores de
   * pantalla. Aprovecharlo evita que cada pantalla tenga que acordarse de pedir
   * la hoja alta —y olvidarse es exactamente lo que produce el desplegable
   * cortado por la mitad—.
   *
   * Va en una regla aparte de la de arriba, y no en la misma lista de
   * selectores, por una razón concreta: un navegador sin `:has()` descarta la
   * regla **entera** donde aparezca, así que compartir la lista se llevaría
   * puesto también el `data-mobile-height="tall"` explícito, que no necesita
   * `:has()` para nada.
   */
  .ds-modal[data-mobile="sheet"]:has([aria-haspopup="listbox"], [aria-haspopup="dialog"], [role="combobox"]),
  .ds-drawer[data-mobile="sheet"]:has([aria-haspopup="listbox"], [aria-haspopup="dialog"], [role="combobox"]) {
    height: 92vh;
    height: 92dvh;
  }

  .ds-modal[data-mobile="fullscreen"],
  .ds-drawer[data-mobile="fullscreen"] {
    inset: 0;
    width: 100vw;
    max-width: 100vw;
    height: 100vh;
    height: 100dvh;
    max-height: 100vh;
    max-height: 100dvh;
    margin: 0;
    border-radius: 0;
    animation-name: ds-sheet-in;
  }

  /* Con alto definido, el interior lo llena: el mismo motivo que en el panel
     lateral de escritorio. Sin esto, el pie flotaría a media hoja. */
  .ds-modal[data-mobile="fullscreen"] .ds-overlay__inner,
  .ds-drawer[data-mobile="fullscreen"] .ds-overlay__inner,
  .ds-modal[data-mobile="sheet"][data-mobile-height="tall"] .ds-overlay__inner,
  .ds-drawer[data-mobile="sheet"][data-mobile-height="tall"] .ds-overlay__inner {
    height: 100%;
  }

  /* Lo mismo para la hoja que se hizo alta sola, en su propia regla por el
     motivo de siempre: sin `:has()` esta se descarta y la de arriba sobrevive. */
  .ds-modal[data-mobile="sheet"]:has([aria-haspopup="listbox"], [aria-haspopup="dialog"], [role="combobox"]) .ds-overlay__inner,
  .ds-drawer[data-mobile="sheet"]:has([aria-haspopup="listbox"], [aria-haspopup="dialog"], [role="combobox"]) .ds-overlay__inner {
    height: 100%;
  }

  /* En pantalla completa el encabezado toca la muesca. */
  .ds-modal[data-mobile="fullscreen"] .ds-overlay__header,
  .ds-drawer[data-mobile="fullscreen"] .ds-overlay__header {
    padding-top: calc(var(--ds-space-4) + var(--ds-safe-top));
  }

  /* El agarre solo existe donde se puede arrastrar. */
  .ds-modal[data-mobile="sheet"] .ds-sheet-grip,
  .ds-drawer[data-mobile="sheet"] .ds-sheet-grip {
    display: flex;
  }

  /* El encabezado de una hoja arrastrable no debe seleccionar texto al
     mantener el dedo: el gesto es arrastrar, no marcar. */
  .ds-modal[data-mobile="sheet"] .ds-overlay__header,
  .ds-drawer[data-mobile="sheet"] .ds-overlay__header {
    user-select: none;
    touch-action: none;
  }

  /* El pie de una hoja es la barra de acciones del teléfono: a lo ancho. */
  .ds-modal[data-mobile] .ds-overlay__footer > .ds-btn,
  .ds-drawer[data-mobile] .ds-overlay__footer > .ds-btn {
    flex: 1 1 auto;
  }

  /*
   * En el pie, el nombre corto entra desde el corte móvil y no desde el de
   * teléfono en vertical (479.98px).
   *
   * Es el único lugar donde tres o cuatro botones comparten una fila, y ahí la
   * cuenta no da: con «El doctor no estaba», «Guardar y seguir después» y
   * «Completar cita» el pie se parte en **tres** filas a 500px de ancho, y el
   * botón que importa queda debajo del pliegue. En una cabecera de tarjeta, con
   * uno o dos botones, el nombre largo sigue entrando y por eso la regla no es
   * general.
   *
   * El nombre completo no se pierde: sigue en el `title` del botón y en su
   * etiqueta accesible.
   */
  .ds-overlay__footer .ds-btn__label--long { display: none; }
  .ds-overlay__footer .ds-btn__label--short { display: inline; }

  .ds-overlay__header,
  .ds-overlay__body,
  .ds-overlay__footer {
    padding-inline: var(--ds-space-4);
  }
}

@keyframes ds-sheet-in {
  from { transform: translateY(100%); }
}

/* ---------- Agarre y arrastre ---------- */

/*
 * La barrita de arriba. No es decoración: es lo que anuncia que la hoja se
 * puede bajar con el dedo, un gesto que de otro modo nadie descubre. Se dibuja
 * siempre y el `@media` de arriba la muestra solo donde el gesto existe.
 */
.ds-sheet-grip {
  display: none;
  align-items: center;
  justify-content: center;
  padding: var(--ds-space-2) 0 var(--ds-space-1);
  flex-shrink: 0;
  cursor: grab;
  touch-action: none;
  user-select: none;
}

.ds-sheet-grip::before {
  content: "";
  width: 36px;
  height: 4px;
  border-radius: var(--ds-radius-full);
  background: var(--ds-border-strong);
}

/* Mientras el dedo manda, el elemento sigue al dedo sin interpolación. */
.ds-modal[data-dragging="true"],
.ds-drawer[data-dragging="true"] {
  animation: none;
  transition: none;
}

/* Al soltar: vuelve a su sitio, o termina de bajarse. */
.ds-modal[data-settling="true"],
.ds-drawer[data-settling="true"] {
  animation: none;
  transition: transform var(--ds-duration-base) var(--ds-ease);
}

/*
 * La hoja ya subió: la animación de entrada no vuelve a correr.
 *
 * Las dos reglas de arriba apagan la animación mientras el gesto dura, y ahí
 * está la trampa: cuando el atributo se quita, `animation-name: ds-sheet-in`
 * vuelve a aplicarse y el navegador la **arranca de nuevo**. La hoja baja fuera
 * de la pantalla y sube otra vez, en medio de una sesión, sin que nadie haya
 * pedido abrir nada. Se reportó al tocar el encabezado, que era el camino más
 * corto para entrar y salir del estado de arrastre.
 *
 * `overlay.js` pone esta marca al empezar el arrastre y la quita en `open()`,
 * así que cada apertura anima una sola vez. Va después de las dos reglas de
 * arriba porque comparte su especificidad con `[data-mobile="sheet"]`: lo que
 * decide es el orden.
 */
.ds-modal[data-entered="true"],
.ds-drawer[data-entered="true"] {
  animation: none;
}

/* El pie es lo último que toca el dedo: respeta el área segura del teléfono. */
@supports (padding: max(0px)) {
  .ds-overlay__footer {
    padding-bottom: max(var(--ds-space-3), var(--ds-safe-bottom));
  }
}

/* Sin pie, el que respeta el área segura es el cuerpo. */
@supports (padding: max(0px)) {
  .ds-overlay__inner:not(:has(.ds-overlay__footer)) .ds-overlay__body {
    padding-bottom: max(var(--ds-space-5), var(--ds-safe-bottom));
  }
}

/* ==================== Estructura interna compartida ==================== */

/*
 * Columna flexible y no grilla: los hijos son cuatro y **cambian según el
 * caso** —el agarre solo existe en la hoja arrastrable, el pie solo si hay
 * acciones—, y una `grid-template-rows` fija obliga a redeclarar la plantilla
 * en cada combinación. Con flex, el cuerpo se queda con lo que sobra y el resto
 * ocupa lo suyo, haya tres hijos o cuatro.
 */
.ds-overlay__inner {
  display: flex;
  flex-direction: column;
  /* Hereda el tope del diálogo: es lo que acota la columna y hace que el cuerpo
     tenga scroll propio en lugar de desbordar la caja. */
  max-height: inherit;
  min-height: 0;
}

/*
 * El `height: 100%` va **solo** en el panel lateral, que sí declara un alto
 * definido (`100dvh`). En el modal, cuyo alto lo da el contenido, un porcentaje
 * de alto hace que WebKit lo calcule como cero y lo aplaste.
 */
.ds-drawer .ds-overlay__inner {
  height: 100%;
}

.ds-overlay__header {
  display: flex;
  align-items: flex-start;
  gap: var(--ds-space-3);
  padding: var(--ds-space-4) var(--ds-space-5);
  border-bottom: 1px solid var(--ds-border);
  flex-shrink: 0;
}

.ds-overlay__header-text {
  display: grid;
  gap: 2px;
  min-width: 0;
  flex: 1;
}

.ds-overlay__title {
  font-size: var(--ds-text-lg);
  font-weight: var(--ds-weight-semibold);
  line-height: var(--ds-leading-tight);
}

.ds-overlay__description {
  font-size: var(--ds-text-sm);
  color: var(--ds-text-muted);
}

.ds-overlay__close {
  flex-shrink: 0;
  margin: -4px -8px -4px 0;
}

.ds-overlay__body {
  padding: var(--ds-space-5);
  overflow-y: auto;
  min-height: 0;
  flex: 1 1 auto;
  /*
   * El scroll no se encadena con la página de atrás: sin esto, llegar al final
   * de la lista sigue desplazando el fondo y en iOS el rebote arrastra la hoja
   * entera mientras se lee.
   */
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
}

.ds-overlay__footer {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--ds-space-2);
  padding: var(--ds-space-3) var(--ds-space-5) var(--ds-space-4);
  /* Ni línea ni tono propio: lo separa el aire, y así el botón secundario
     —que ahora es gris— no compite con su propio fondo. */
  flex-wrap: wrap;
  flex-shrink: 0;
}

.ds-overlay__footer--between { justify-content: space-between; }

/* Bloquea el scroll del fondo mientras hay un overlay abierto. */
body.ds-scroll-locked {
  overflow: hidden;
}

/* ==================== Toasts ==================== */

/*
 * Los avisos se **apilan**, no se encolumnan.
 *
 * En columna, tres avisos seguidos son tres cajas que ocupan media pantalla y
 * empujan a las de abajo mientras se leen. Apilados ocupan lo que ocupa uno: el
 * último adelante, los anteriores asomando por arriba, cada uno un poco más
 * chico. Se ve cuántos hay sin que ninguno tape nada, y se van tocando: cada
 * toque cierra el de adelante y trae el siguiente.
 *
 * La posición de cada uno la calcula `toast.js`, porque depende de cuántos haya
 * y de en qué lugar de la pila esté; el CSS pone la transición para que ese
 * cambio de lugar se vea.
 */
.ds-toaster {
  position: fixed;
  bottom: var(--ds-space-5);
  right: var(--ds-space-5);
  z-index: var(--ds-z-toast);
  width: min(360px, calc(100vw - 2 * var(--ds-space-4)));
  pointer-events: none;
}

.ds-toast {
  position: absolute;
  inset: auto 0 0 0;
  display: flex;
  align-items: flex-start;
  gap: var(--ds-space-3);
  padding: var(--ds-space-3) var(--ds-space-4);
  background: var(--ds-surface);
  /* La única línea que queda es la que dice de qué tipo es el aviso. */
  border-left: 3px solid var(--ds-text-subtle);
  border-radius: var(--ds-radius-lg);
  box-shadow: var(--ds-shadow-lg);
  font-size: var(--ds-text-md);
  /* Se apila desde abajo: los de atrás asoman por arriba. */
  transform-origin: bottom center;
  cursor: pointer;
  transition: transform var(--ds-duration-base) var(--ds-ease),
              opacity var(--ds-duration-base) var(--ds-ease);
}

.ds-toast--success { border-left-color: var(--ds-success); }
.ds-toast--error   { border-left-color: var(--ds-danger); }
.ds-toast--info    { border-left-color: var(--ds-info); }

.ds-toast__body { flex: 1; min-width: 0; overflow-wrap: anywhere; }

.ds-toast__title { font-weight: var(--ds-weight-semibold); }

/* Acción dentro del toast: para lo que se avisa y se puede resolver ahí mismo. */
.ds-toast__actions {
  display: flex;
  gap: var(--ds-space-2);
  margin-top: var(--ds-space-2);
}

.ds-toast__close {
  flex-shrink: 0;
  margin: -4px -8px -4px 0;
}

/*
 * En el teléfono el aviso ocupa el ancho y se sienta **por encima de la barra de
 * pestañas**: si sale debajo, la barra lo tapa justo cuando trae el «deshacer».
 */
@media (max-width: 767.98px) {
  .ds-toaster {
    left: var(--ds-space-3);
    right: var(--ds-space-3);
    bottom: calc(var(--ds-tabbar-clearance) + var(--ds-space-1));
    width: auto;
  }
}

/* ==================== Diálogo de opciones ====================
 *
 * El contenido del botón de configuración de la topbar: lo que se cambia una vez
 * y no se vuelve a tocar. Es el mismo diálogo en todos los anchos —modal chico
 * en escritorio, hoja inferior en el teléfono— y por eso no necesita una versión
 * de móvil aparte.
 */

.ds-options {
  display: grid;
  gap: var(--ds-space-5);
}

.ds-options__group {
  display: grid;
  gap: var(--ds-space-2);
}

.ds-options__label {
  font-size: var(--ds-text-xs);
  font-weight: var(--ds-weight-semibold);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--ds-text-subtle);
}

/*
 * Lo que la aplicación agregue por su cuenta: la versión, el correo, salir.
 * Va al final y separado, porque son cosas heterogéneas que no forman un grupo
 * con nombre y mezclarlas con el tema haría creer que son ajustes.
 */
.ds-options__extras {
  display: grid;
  gap: var(--ds-space-2);
  padding-top: var(--ds-space-4);
  border-top: 1px solid var(--ds-border);
}

/* ==================== Hoja de acciones ====================
 *
 * Lo que en escritorio es un menú desplegable de una fila —«Editar», «Duplicar»,
 * «Eliminar»— en el teléfono no puede ser un menú de 28px de alto colgando de un
 * botón de 20px. Es una lista de opciones grandes, y va donde llega el pulgar.
 */

.ds-action-sheet {
  display: grid;
  gap: var(--ds-space-1);
}

.ds-action-sheet__item {
  display: flex;
  align-items: center;
  gap: var(--ds-space-3);
  width: 100%;
  min-height: var(--ds-tap-min);
  padding: var(--ds-space-3);
  border: none;
  border-radius: var(--ds-radius-md);
  background: none;
  color: var(--ds-text);
  font-size: var(--ds-text-base);
  font-weight: var(--ds-weight-medium);
  text-align: left;
  text-decoration: none;
  cursor: pointer;
  touch-action: manipulation;
  transition: background var(--ds-duration-fast) var(--ds-ease);
}

.ds-action-sheet__item:hover { background: var(--ds-surface-hover); text-decoration: none; }

.ds-action-sheet__item--danger { color: var(--ds-danger); }

.ds-action-sheet__item:disabled {
  color: var(--ds-text-subtle);
  cursor: not-allowed;
}

/*
 * El icono de cada opción, **en un círculo**.
 *
 * Ancho y alto iguales, que es lo único que hace que el fondo del hover y el del
 * toque sean un círculo y no una pastilla. Antes era `width: 20px` sin alto: la
 * caja medía 20×18 —el alto se lo daba el propio SVG—, así que el icono quedaba
 * estrecho contra el texto y no había forma de que el estado se leyera como algo.
 *
 * El círculo está siempre, en gris, y se tiñe al pasar por encima o al tocar: un
 * fondo que aparece de la nada mueve la vista, y uno que solo cambia de color
 * confirma dónde está el dedo sin correr nada de lugar.
 */
.ds-action-sheet__icon {
  display: grid;
  place-items: center;
  flex-shrink: 0;
  width: 36px;
  height: 36px;
  border-radius: var(--ds-radius-full);
  background: var(--ds-surface-hover);
  color: var(--ds-text-muted);
  transition: background var(--ds-duration-fast) var(--ds-ease),
              color var(--ds-duration-fast) var(--ds-ease);
}

.ds-action-sheet__item:hover .ds-action-sheet__icon,
.ds-action-sheet__item:active .ds-action-sheet__icon,
.ds-action-sheet__item:focus-visible .ds-action-sheet__icon {
  background: var(--ds-accent-soft);
  color: var(--ds-accent);
}

.ds-action-sheet__item--danger:hover .ds-action-sheet__icon,
.ds-action-sheet__item--danger:active .ds-action-sheet__icon {
  background: var(--ds-danger-soft);
  color: var(--ds-danger);
}

.ds-action-sheet__item--danger .ds-action-sheet__icon { color: currentcolor; }

.ds-action-sheet__text { display: grid; gap: 1px; min-width: 0; }

.ds-action-sheet__description {
  font-size: var(--ds-text-sm);
  font-weight: var(--ds-weight-normal);
  color: var(--ds-text-muted);
}
