/**
 * ui.css — la piel de la capa de superposición (app-ui.js).
 *
 * Aquí vive TODO lo visual de avisar, confirmar y preguntar, una sola vez. Antes
 * estaba en cuatro sitios: estilos escritos en JavaScript (el toast del panel), CSS
 * inyectado por JavaScript (el del sitio), un `#0f172a` clavado en `pw-tools.js`, y
 * 35 reglas de `.confirm-dialog` en la hoja del panel — la razón por la que una
 * pieza del sitio público salió a producción sin un solo estilo el 2026-08-12.
 *
 * ── Cómo se viste ────────────────────────────────────────────────────────────
 * Todo entra por tokens `--ui-*` con el valor de hoy como respaldo, igual que las
 * familias `--cc-*`, `--ha-*` y `--ni-*`: el panel los mapea a los suyos en
 * `cms.css` y conserva su aspecto exacto; un tema los mapea a su paleta. La
 * ESTRUCTURA y el COMPORTAMIENTO son del core; el color y la colocación, de quien
 * pinta (contrato-base §14).
 *
 * ── Por qué no hay `inset`, ni `gap` en flex, ni `:is()` ─────────────────────
 * El suelo de soporte está declarado y vigilado (`JsBaselineTest` para el JS; esta
 * cabecera para la hoja): tablets y móviles que se quedaron en iOS 12. Allí
 * `inset` (Safari 14.1) no aplica —el velo no cubriría la pantalla— y `gap` en
 * flexbox (Safari 14.1) tampoco: las cosas se pegarían unas a otras. Cuestan cero
 * de evitar y su ausencia no se ve desde un portátil moderno, que es justo lo que
 * las hace peligrosas. `cms.css` usa las dos en `.confirm-dialog`; por eso el
 * diálogo pasa a esta hoja.
 *
 * Sólo se anima `opacity` y `transform`: las dos que un aparato de 2016 mueve en el
 * compositor sin repintar.
 */

:root {
  --ui-motion: 200ms;
  --ui-ease: cubic-bezier(.2, .8, .3, 1);
}

/* El portal. No intercepta nada por sí mismo: cada pieza reactiva lo suyo. */
.ui-layer {
  position: fixed;
  top: 0; right: 0; bottom: 0; left: 0;
  z-index: var(--ui-z, 3000);
  pointer-events: none;
}

/* ── TOAST ──────────────────────────────────────────────────────────────────
   Posición por tokens: el sitio público lo quiere abajo centrado y el panel
   abajo a la derecha, y ninguno de los dos cambia de sitio al unificarse el
   mecanismo — mover algo que un cliente ya tiene aprendido sería degradar.
   `--app-bottom-inset` es lo que publica cualquier pieza que ocupe el borde
   inferior (el banner de consentimiento lo hace): el aviso monta encima en vez
   de aterrizar sobre ella. */
.ui-toasts {
  position: fixed;
  bottom: calc(var(--ui-toast-bottom, 24px) + var(--app-bottom-inset, 0px) + var(--ui-discover-inset, 0px));
  left: var(--ui-toast-left, 50%);
  right: var(--ui-toast-right, auto);
  transform: translateX(var(--ui-toast-shift, -50%));
  display: flex;
  flex-direction: column;
  align-items: var(--ui-toast-align, center);
  max-width: min(92vw, 420px);
  pointer-events: none;
}

.ui-toast {
  display: flex;
  align-items: center;
  max-width: 100%;
  margin-top: 8px;              /* en vez de `gap`: no existe en el suelo declarado */
  padding: var(--ui-toast-padding, 12px 20px);
  background: var(--ui-toast-bg, #0f172a);
  color: var(--ui-toast-ink, #fff);
  border: 1px solid var(--ui-toast-border, rgba(255, 255, 255, .1));
  border-radius: var(--ui-toast-radius, 0);
  box-shadow: var(--ui-toast-shadow, 0 10px 30px rgba(0, 0, 0, .25));
  font-size: var(--ui-toast-size, 14px);
  font-weight: 500;
  line-height: 1.4;
  text-align: left;
  opacity: 0;
  transform: translateY(12px);
  transition: opacity var(--ui-motion) var(--ui-ease), transform var(--ui-motion) var(--ui-ease);
  pointer-events: auto;
}
.ui-toast.is-visible { opacity: 1; transform: translateY(0); }

/* El par fondo + tinta va SIEMPRE junto: un fondo sin su tinta es como se rompe
   un contraste, y ya pasó con el botón del bloque de avisos. */
.ui-toast--success { background: var(--ui-toast-success-bg, #15803d); color: var(--ui-toast-success-ink, #fff); }
.ui-toast--danger,
.ui-toast--error   { background: var(--ui-toast-danger-bg, #b91c1c);  color: var(--ui-toast-danger-ink, #fff); }
.ui-toast--warn    { background: var(--ui-toast-warn-bg, #b45309);    color: var(--ui-toast-warn-ink, #fff); }

.ui-toast__text { flex: 1 1 auto; min-width: 0; }

/* Botón de verdad: alcanzable con el tabulador y anunciado. El "Deshacer" de
   Gmail y el snackbar de Material 3. */
.ui-toast__action {
  flex: 0 0 auto;
  margin-left: 16px;
  padding: 6px 10px;
  min-height: 32px;
  background: none;
  border: 1px solid currentColor;
  border-radius: var(--ui-toast-radius, 0);
  color: inherit;
  font: inherit;
  font-weight: 700;
  cursor: pointer;
}
.ui-toast__action:focus-visible { outline: 2px solid currentColor; outline-offset: 2px; }

/* ── DIÁLOGO ────────────────────────────────────────────────────────────────
   Modal de verdad: el velo tapa, el foco se queda dentro (app-ui.js) y el fondo
   deja de existir para un lector de pantalla. */
.ui-dialog {
  position: fixed;
  top: 0; right: 0; bottom: 0; left: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
  background: var(--ui-scrim, rgba(0, 0, 0, .45));
  -webkit-backdrop-filter: blur(3px);
  backdrop-filter: blur(3px);
  opacity: 0;
  transition: opacity var(--ui-motion) var(--ui-ease);
  pointer-events: auto;
}
.ui-dialog.is-open { opacity: 1; }
.ui-dialog[hidden] { display: none; }

.ui-dialog__box {
  width: 100%;
  max-width: var(--ui-dialog-width, 360px);
  padding: var(--ui-dialog-padding, 24px);
  background: var(--ui-surface, #fff);
  color: var(--ui-ink, #0f172a);
  border: 1px solid var(--ui-border, rgba(0, 0, 0, .12));
  border-radius: var(--ui-dialog-radius, 8px);
  box-shadow: var(--ui-dialog-shadow, 0 24px 60px rgba(0, 0, 0, .35));
  text-align: center;
  transform: scale(.96) translateY(-4px);
  transition: transform var(--ui-motion) var(--ui-ease);
}
.ui-dialog.is-open .ui-dialog__box { transform: scale(1) translateY(0); }

.ui-dialog__icon {
  width: 44px; height: 44px;
  margin: 0 auto 16px;
  display: flex; align-items: center; justify-content: center;
  border-radius: 999px;
  background: var(--ui-accent-dim, rgba(37, 99, 235, .12));
  border: 1px solid var(--ui-accent-edge, rgba(37, 99, 235, .25));
}
.ui-dialog__icon.is-danger {
  background: var(--ui-danger-dim, rgba(248, 113, 113, .12));
  border-color: var(--ui-danger-edge, rgba(248, 113, 113, .25));
}
.ui-dialog__icon svg {
  width: 20px; height: 20px;
  fill: none;
  stroke: var(--ui-accent, #2563eb);
  stroke-width: 2; stroke-linecap: round;
}
.ui-dialog__icon.is-danger svg { stroke: var(--ui-danger, #dc2626); }

.ui-dialog__title {
  margin: 0;
  font-size: var(--ui-title-size, 16px);
  font-weight: 600;
  color: var(--ui-ink, #0f172a);
}
.ui-dialog__sub {
  margin: 8px 0 0;
  font-size: var(--ui-sub-size, 14px);
  color: var(--ui-muted, #64748b);
}
.ui-dialog__sub[hidden] { display: none; }

.ui-dialog__input { margin-top: 16px; }
.ui-dialog__input[hidden] { display: none; }
.ui-dialog__field {
  width: 100%;
  min-height: 44px;               /* área táctil: la misma regla que el banner */
  padding: 10px 12px;
  background: var(--ui-field-bg, #fff);
  color: var(--ui-ink, #0f172a);
  border: 1px solid var(--ui-border, rgba(0, 0, 0, .2));
  border-radius: var(--ui-field-radius, 6px);
  font: inherit;
  text-align: center;
}

/* Las casillas de `AppUI.ask`. `[hidden]` con su contrapartida EXPLÍCITA porque
   este bloque declara `display`: sin ella el atributo no gana y las casillas se
   ven en un `confirm` que no las pidió — es la mordida que esta hoja ya lleva
   cinco veces (`.file-drop`, `.media-preview*`), y aquí nace cerrada. */
.ui-dialog__checks {
  margin-top: 16px;
  display: block;
  text-align: left;
}
.ui-dialog__checks[hidden] { display: none; }

.ui-dialog__check {
  display: flex;
  align-items: flex-start;
  min-height: 44px;               /* área táctil: la misma regla que el campo */
  padding: 6px 0;
  cursor: pointer;
}
.ui-dialog__check-box {
  width: 18px;
  height: 18px;
  margin: 3px 10px 0 0;
  flex: none;                     /* `flex-shrink` vale 1 por defecto: sin esto
                                     la casilla se estruja hasta 0 px cuando el
                                     rótulo es largo — medido en el panel */
  accent-color: var(--ui-accent, #2563eb);
}
.ui-dialog__check-text { display: block; }
.ui-dialog__check-label {
  display: block;
  color: var(--ui-ink, #0f172a);
}
.ui-dialog__check-sub {
  display: block;
  margin-top: 2px;
  font-size: .875em;
  color: var(--ui-ink-dim, #64748b);
}

.ui-dialog__actions {
  margin-top: 20px;
  display: flex;
  justify-content: center;
}

.ui-btn {
  min-height: 44px;
  padding: 10px 18px;
  margin: 0 4px;                  /* en vez de `gap` */
  border-radius: var(--ui-btn-radius, 6px);
  border: 1px solid transparent;
  font: inherit;
  font-weight: 600;
  cursor: pointer;
}
.ui-btn:focus-visible { outline: 2px solid var(--ui-accent, #2563eb); outline-offset: 2px; }
.ui-btn--primary { background: var(--ui-accent, #2563eb); color: var(--ui-accent-ink, #fff); }
.ui-btn--danger  { background: var(--ui-danger, #dc2626); color: var(--ui-danger-ink, #fff); }
/* Respaldo con BORDE y no con `currentColor`: un fondo que se deduce de la tinta
   del contexto no puede garantizar su propio contraste — se pagó en producción con
   un botón blanco sobre blanco (FactoryPartialContrastTest). */
.ui-btn--ghost {
  background: none;
  color: var(--ui-ink, #0f172a);
  border-color: var(--ui-border, rgba(0, 0, 0, .2));
}

.ui-dialog__feedback {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-height: 160px;
}
.ui-dialog__feedback[hidden] { display: none; }

.ui-spinner {
  width: 28px; height: 28px;
  border: 3px solid var(--ui-border, rgba(0, 0, 0, .15));
  border-top-color: var(--ui-accent, #2563eb);
  border-radius: 999px;
  animation: ui-spin .8s linear infinite;
}
.ui-spinner[hidden] { display: none; }
@keyframes ui-spin { to { transform: rotate(360deg); } }

.ui-dialog__result { text-align: center; }
.ui-dialog__result[hidden] { display: none; }
.ui-dialog__result-icon {
  width: 48px; height: 48px;
  margin: 0 auto 12px;
  display: flex; align-items: center; justify-content: center;
  border-radius: 999px;
}
.ui-dialog__result-icon.is-success { background: var(--ui-success-dim, rgba(21, 128, 61, .12)); }
.ui-dialog__result-icon.is-failure { background: var(--ui-danger-dim, rgba(220, 38, 38, .12)); }
.ui-dialog__result-icon svg { width: 24px; height: 24px; fill: none; stroke-width: 2.5; stroke-linecap: round; }
.ui-dialog__result-icon.is-success svg { stroke: var(--ui-success, #15803d); }
.ui-dialog__result-icon.is-failure svg { stroke: var(--ui-danger, #dc2626); }
.ui-dialog__result-msg { margin: 0 0 16px; font-size: var(--ui-sub-size, 14px); color: var(--ui-ink, #0f172a); }

/* Movimiento reducido: la capa ya salta al estado final por JavaScript; esto es
   la otra mitad, para la animación puramente declarativa. WCAG 2.3.3. */
@media (prefers-reduced-motion: reduce) {
  .ui-toast, .ui-dialog, .ui-dialog__box { transition: none; }
  .ui-spinner { animation-duration: 2s; }
}

/* ─────────────────────────────────────────────────────────────────────────────
   Tarjeta de descubrimiento — arriba a la derecha, y no es un toast

   Un toast vive abajo, dura cuatro segundos y confirma algo que acabas de hacer.
   Esto vive arriba a la derecha, se queda hasta que decides, y ofrece algo que
   todavía no sabías que existía. Dos gramáticas, dos sitios: exactamente lo que
   hace Firefox con «Get your local weather forecast» y el `cornerExpand` de
   Codrops que este proyecto guarda desde hace meses en docs/design/.

   Debajo de la franja del navegador y por encima de todo lo demás; en un móvil
   ocupa el ancho menos los márgenes, porque una tarjeta de 320 px en una
   pantalla de 360 se lee como un error de maquetación.
   ───────────────────────────────────────────────────────────────────────────── */
.ui-discover {
    position: fixed;
    top: var(--ui-discover-top, 16px);
    right: var(--ui-discover-right, 16px);
    left: auto;
    width: min(20rem, calc(100vw - 32px));
    box-sizing: border-box;
    padding: var(--ui-discover-pad, 16px);
    background: var(--ui-surface, #fff);
    color: var(--ui-ink, #0f172a);
    border: 1px solid var(--ui-border, rgba(15, 23, 42, .1));
    border-radius: var(--ui-radius, 14px);
    box-shadow: var(--ui-shadow, 0 12px 32px rgba(15, 23, 42, .18));
    z-index: var(--ui-z, 2147483000);
    /* 🔴 SIN ESTO LA TARJETA NO RECIBE UN SOLO TOQUE.
       Vive dentro de `.ui-layer`, que es `pointer-events: none` a propósito para
       que un toast no bloquee la página que hay debajo. El toast y el diálogo lo
       reactivan; esta tarjeta NO lo hacía, así que se pintaba perfecta y todos
       los toques la atravesaban: ni «elegir idioma» ni «así está bien»
       respondían, y no fallaba nada ni se registraba nada.
       Lo trajo Adán usando el sitio (2026-08-25). Se llevaba por delante las DOS
       piezas que usan la tarjeta: la oferta de idioma y la invitación de huella,
       o sea que la huella llevaba desde el día 24 sin poder aceptarse. */
    pointer-events: auto;
    /* Entra desde su propia esquina: el movimiento dice de dónde viene la cosa. */
    opacity: 0;
    transform: translate3d(12px, -8px, 0) scale(.98);
    transition: opacity var(--ui-motion, 200ms) ease, transform var(--ui-motion, 200ms) ease;
}
.ui-discover.is-in { opacity: 1; transform: none; }

/* ── En un MÓVIL la tarjeta va ABAJO, y no es una preferencia estética ──────
   Medido el 2026-08-28 sobre `/admin/elv/new` a 393 px: anclada arriba ocupaba
   361 px de 393 de ancho y 220 de alto, o sea que tapaba el título de la
   pantalla, la cabecera de la primera tarjeta y el campo `Name` — el primer
   campo del formulario que alguien está rellenando MIENTRAS habla por teléfono
   con el cliente. Arriba viven la navegación y el título: taparlos desorienta
   justo a quien acaba de llegar, y es la zona más difícil de alcanzar con el
   pulgar (thumb zone, Hoober). Abajo es donde el canon móvil pone una oferta
   que no bloquea: bottom sheet de Material 3, sheet de las HIG de Apple.

   El corte es 768 porque es el escalón declarado del sistema
   (`onlainez-docs/reglas/breakpoints-y-dispositivos-de-referencia.md` §1), no un
   número elegido para este caso. En tablet y escritorio la esquina superior
   derecha sigue siendo correcta —canon del doorhanger de Firefox— y no se toca.

   Respeta `--app-bottom-inset`, que es lo que publica cualquier pieza del borde
   inferior (banner de consentimiento, barra de acciones): la tarjeta monta
   encima en vez de aterrizar sobre ella. Y el toast monta encima de la tarjeta
   vía `--ui-discover-inset`, porque un aviso que se va no puede tapar una
   pregunta que espera. */
@media (max-width: 767px) {
    .ui-discover {
        top: auto;
        bottom: calc(var(--app-bottom-inset, 0px) + var(--ui-discover-bottom, 16px));
        left: var(--ui-discover-left, 16px);
        right: var(--ui-discover-right, 16px);
        width: auto;
        /* Entra desde su borde, como la hoja inferior que es. */
        transform: translate3d(0, 12px, 0) scale(.98);
    }
}

.ui-discover__title { margin: 0; font-weight: 700; font-size: .95rem; line-height: 1.35; }
.ui-discover__body  { margin: .35rem 0 0; font-size: .85rem; line-height: 1.5; color: var(--ui-muted, #64748b); }
.ui-discover__row   { display: flex; align-items: center; gap: .5rem; margin-top: .85rem; }

.ui-discover__cta {
    /* 44px de zona táctil: el suelo para un pulgar (WCAG 2.5.5 / HIG). */
    min-height: 44px; display: inline-flex; align-items: center;
    padding: .55rem 1rem; font: inherit; font-size: .85rem; font-weight: 700;
    color: var(--ui-accent-ink, #fff); background: var(--ui-accent, #4f46e5);
    border: 1px solid transparent; border-radius: 10px; cursor: pointer; text-decoration: none;
}
.ui-discover__dismiss {
    min-height: 44px; padding: .55rem .75rem; margin-left: auto;
    font: inherit; font-size: .85rem; color: var(--ui-muted, #64748b);
    background: none; border: 0; cursor: pointer;
}
.ui-discover__cta:focus-visible,
.ui-discover__dismiss:focus-visible { outline: 2px solid var(--ui-accent, #4f46e5); outline-offset: 2px; }

@media (max-width: 480px) {
    .ui-discover { left: 16px; right: 16px; width: auto; }
}

/* Quien pidió no moverse, no se mueve: el estado final, sin transición. */
@media (prefers-reduced-motion: reduce) {
    .ui-discover { transition: none; }
}
