/* ==========================================================
 * Base común del panel, de /crear, de la pantalla de hostess y de la
 * consola (plan UX, fase 0). Reglas que TODAS las pantallas necesitan y que no
 * caben en una clase de Tailwind. Se carga desde los layouts, antes de
 * que Tailwind (CDN) inyecte las suyas; por eso las que compiten con una
 * utilidad llevan !important o más especificidad.
 * ========================================================== */

/* ==========================================================
 * Modo oscuro: los tokens (PROMPT_MODO_OSCURO.md, fase 1)
 *
 * Un solo juego de colores semánticos que Tailwind expone como clases
 * (`bg-surface`, `text-ink`, `border-line`, `bg-accent`…; ver
 * tailwind.config.js) y que cambia de valor con el tema. Tres bloques, en
 * este orden:
 *   1. `:root`                  claro (por omisión)
 *   2. `@media (prefers-color-scheme: dark)`  el sistema pide oscuro y la
 *      persona NO fijó "claro" (`data-theme="light"`)
 *   3. `:root[data-theme="dark"]`  la persona fijó "oscuro"
 * El servidor pone `data-theme` en <html> (theme.middleware.js); con
 * "Sistema" no hay atributo y decide el @media. Paleta oscura: la de
 * prototipos/festelia-lienzo.html.
 * Los bloques 2 y 3 llevan los MISMOS valores: si cambias uno, cambia el
 * otro (scripts/theme-checks.js lo comprueba).
 * ========================================================== */
:root {
  color-scheme: light;
  --bg: #f7f5fc;
  --surface: #ffffff;
  --surface2: #f0ecfa;
  --ink: #1d1830;
  --muted: #6a6482;
  --line: #d4cae9;
  --accent: #7c3aed;
  --accent-ink: #ffffff;
  --accent-soft: #ede7fd;
  --accent-strong: #6d28d9;
  --ok: #0f766e;
  --ok-soft: #ecfdf5;
  --ok-line: #a7f3d0;
  --warn: #b45309;
  --warn-soft: #fffbeb;
  --warn-line: #fcd34d;
  --danger: #b91c1c;
  --danger-soft: #fef2f2;
  --danger-line: #fecaca;
  --info: #0369a1;
  --info-soft: #f0f9ff;
  --info-line: #bae6fd;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;
    --bg: #120e1f;
    --surface: #1c1630;
    --surface2: #261e40;
    --ink: #ece8f8;
    --muted: #a59ec0;
    --line: #40345f;
    --accent: #a78bfa;
    --accent-ink: #1a1033;
    --accent-soft: #2e2452;
    --accent-strong: #c4b5fd;
    --ok: #5eead4;
    --ok-soft: #10302c;
    --ok-line: #1f5b53;
    --warn: #fbbf24;
    --warn-soft: #33260d;
    --warn-line: #6b4d12;
    --danger: #fca5a5;
    --danger-soft: #3b1620;
    --danger-line: #70303c;
    --info: #7dd3fc;
    --info-soft: #0c2a3d;
    --info-line: #1e5575;
  }
}
:root[data-theme="dark"] {
  color-scheme: dark;
  --bg: #120e1f;
  --surface: #1c1630;
  --surface2: #261e40;
  --ink: #ece8f8;
  --muted: #a59ec0;
  --line: #40345f;
  --accent: #a78bfa;
  --accent-ink: #1a1033;
  --accent-soft: #2e2452;
  --accent-strong: #c4b5fd;
  --ok: #5eead4;
  --ok-soft: #10302c;
  --ok-line: #1f5b53;
  --warn: #fbbf24;
  --warn-soft: #33260d;
  --warn-line: #6b4d12;
  --danger: #fca5a5;
  --danger-soft: #3b1620;
  --danger-line: #70303c;
  --info: #7dd3fc;
  --info-soft: #0c2a3d;
  --info-line: #1e5575;
}
/* Campos de formulario: fondo y borde con los tokens. `:where` los deja en
   especificidad cero, así cualquier utilidad (`bg-surface2`,
   `border-danger`) gana sin !important. Las casillas y los botones de
   radio se pintan solos con `color-scheme`. */
:where(input:not([type=checkbox]):not([type=radio]):not([type=range]):not([type=color]):not([type=submit]):not([type=button]), select, textarea) {
  background-color: var(--surface);
  color: var(--ink);
}
::placeholder { color: var(--muted); opacity: 0.75; }
:where(input:disabled, select:disabled, textarea:disabled) { background-color: var(--surface2); }
::selection { background: var(--accent-soft); color: var(--ink); }

/* Conmutador de tema: luna cuando se ve claro (toca para oscurecer), sol
   cuando se ve oscuro. Sigue las mismas tres reglas que los tokens. */
.fx-theme-sun { display: none; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .fx-theme-moon { display: none; }
  :root:not([data-theme="light"]) .fx-theme-sun { display: inline-flex; }
}
:root[data-theme="dark"] .fx-theme-moon { display: none; }
:root[data-theme="dark"] .fx-theme-sun { display: inline-flex; }

/* Alpine oculta lo que lleva x-cloak hasta que arranca. Sin esta regla,
   en una red lenta los modales, el menú móvil, el aviso de la prueba y la
   barra de "Guardar cambios" se veían abiertos un instante en cada carga
   (AUDITORIA_UI_UX.md, UX-12). */
[x-cloak] { display: none !important; }

/* iOS hace zoom al enfocar cualquier campo con letra menor a 16 px, y
   casi todo el panel usa `text-sm` (14 px) en sus campos (UX-13). En
   pantallas chicas se sube a 16 px sin tocar cada formulario; en
   escritorio se respeta el tamaño de cada uno. */
@media (max-width: 639px) {
  input:not([type=checkbox]):not([type=radio]):not([type=range]):not([type=color]):not([type=file]):not([type=submit]):not([type=button]),
  select,
  textarea {
    font-size: 16px !important;
  }
}

/* `viewport-fit=cover` (en los layouts) deja que la página use toda la
   pantalla de un iPhone con muesca; a cambio, en horizontal el contenido
   tiene que apartarse de ella por su cuenta. */
body {
  padding-left: env(safe-area-inset-left);
  padding-right: env(safe-area-inset-right);
}

/* Quien pidió menos movimiento en su sistema no recibe animaciones ni
   desplazamientos suaves (UX-43). */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* Controles que aparecen al pasar el mouse. En pantallas táctiles no hay
   "pasar el mouse": ahí se ven siempre. Antes eran invisibles pero
   tocables, y un toque en la foto del álbum aprobaba u ocultaba sin
   verlo (UX-15). */
@media (hover: hover) {
  .hover-reveal { opacity: 0; transition: opacity 150ms ease; }
  .group:hover .hover-reveal,
  .hover-reveal:focus-within { opacity: 1; }
}

/* ---- Plan UX, fase 1: piezas de festelia-ui.js ---- */

/* Con JavaScript, los mensajes del servidor salen como toast: el recuadro
   de arriba se esconde ANTES de pintar (el layout pone `js` en <html>
   desde el <head>) para que no parpadee. Sin JavaScript se queda. */
html.js [data-flash-box] { display: none; }

/* Toasts: abajo al centro, por encima del área segura del iPhone. */
.fx-toasts {
  position: fixed;
  left: 50%;
  transform: translateX(-50%);
  bottom: calc(env(safe-area-inset-bottom) + 1rem);
  z-index: 70;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.5rem;
  width: max-content;
  max-width: calc(100vw - 2rem);
  pointer-events: none;
}
.fx-toast {
  pointer-events: auto;
  display: flex;
  align-items: center;
  gap: 0.75rem;
  max-width: 100%;
  min-height: 44px;
  padding: 0.625rem 0.75rem 0.625rem 1rem;
  border-radius: 0.875rem;
  font-size: 0.875rem;
  line-height: 1.35;
  color: var(--bg);
  background: var(--ink);
  box-shadow: 0 10px 25px -5px rgb(15 23 42 / 0.35);
  animation: fx-toast-in 160ms ease-out;
}
/* Éxito y error: saturados y con letra blanca en los dos temas. */
.fx-toast--success { background: rgb(4 120 87); color: #fff; }
.fx-toast--error { background: rgb(185 28 28); color: #fff; }
.fx-toast__text { flex: 1 1 auto; }
.fx-toast__action,
.fx-toast__close {
  flex: none;
  min-height: 36px;
  min-width: 36px;
  border-radius: 0.5rem;
  font-weight: 600;
  color: inherit;
}
.fx-toast__action { padding: 0 0.5rem; text-decoration: underline; text-underline-offset: 3px; }
.fx-toast__close { font-size: 1.25rem; line-height: 1; opacity: 0.85; }
.fx-toast__action:focus-visible,
.fx-toast__close:focus-visible { outline: 2px solid currentColor; outline-offset: 2px; }
/* Entrada sin señal (entrance-app.js, plan UX, fase 5, UX-37): fijo arriba
   mientras no haya conexión o queden llegadas por enviar. */
.fx-offline {
  position: fixed;
  left: 50%;
  transform: translateX(-50%);
  top: calc(env(safe-area-inset-top) + 0.5rem);
  z-index: 75;
  max-width: calc(100vw - 2rem);
  padding: 0.5rem 1rem;
  border-radius: 9999px;
  font-size: 0.875rem;
  font-weight: 600;
  color: var(--warn);
  background: var(--warn-soft);
  border: 1px solid var(--warn-line);
  box-shadow: 0 8px 20px -6px rgb(15 23 42 / 0.25);
}
.fx-offline[hidden] { display: none; }

/* El indicador de guardado automático (Festelia.saving, fase 4): más
   discreto que un toast y siempre uno solo. */
.fx-save-status {
  min-height: 36px;
  padding: 0.375rem 0.875rem;
  font-size: 0.8125rem;
  background: var(--ink);
}
.fx-save-status[data-state="done"] { background: rgb(4 120 87); color: #fff; }
@keyframes fx-toast-in { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }

/* Aviso fijo de sesión perdida. */
.fx-session-bar {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 80;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  padding: calc(env(safe-area-inset-top) + 0.75rem) 1rem 0.75rem;
  background: var(--warn-soft);
  border-bottom: 1px solid var(--warn-line);
  box-shadow: 0 4px 12px -4px rgb(15 23 42 / 0.2);
}
.fx-session-bar__text { font-size: 0.875rem; color: var(--warn); }
.fx-session-bar__button {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0 1rem;
  border-radius: 0.625rem;
  background: rgb(217 119 6);
  color: #fff;
  font-size: 0.875rem;
  font-weight: 600;
}

/* Hoja inferior (x-sheet). En el celular sale de abajo, a todo lo ancho,
   con scroll propio y respetando el área segura; desde `sm`, centrada.
   festelia-ui.js ajusta `max-height` y `margin-bottom` al teclado. */
.fx-sheet-backdrop {
  position: fixed;
  inset: 0;
  z-index: 50;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  background: rgb(15 23 42 / 0.55);
}
.fx-sheet {
  width: 100%;
  max-height: 92vh;
  max-height: 92dvh;
  overflow-y: auto;
  overscroll-behavior: contain;
  background: var(--surface);
  color: var(--ink);
  border-radius: 1.25rem 1.25rem 0 0;
  padding: 1.25rem 1.25rem calc(env(safe-area-inset-bottom) + 1.25rem);
  box-shadow: 0 -10px 30px -10px rgb(15 23 42 / 0.35);
  animation: fx-sheet-in 180ms ease-out;
}
.fx-sheet:focus { outline: none; }
.fx-sheet__grip {
  width: 2.5rem;
  height: 0.3rem;
  margin: -0.5rem auto 0.75rem;
  border-radius: 9999px;
  background: var(--line);
}
@media (min-width: 640px) {
  .fx-sheet-backdrop { align-items: center; padding: 1rem; }
  .fx-sheet { max-width: 32rem; border-radius: 1.25rem; padding-bottom: 1.25rem; }
  .fx-sheet__grip { display: none; }
}
/* Renglón de acción dentro de una hoja (plan UX, fase 2): ícono + texto,
   48 px de alto, todo el ancho tocable. Se usa en "Más acciones" de
   Invitados. */
.fx-action {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  width: 100%;
  min-height: 48px;
  padding: 0 0.75rem;
  border-radius: 0.75rem;
  font-size: 0.9375rem;
  font-weight: 500;
  text-align: left;
  color: var(--ink);
}
.fx-action:hover,
.fx-action:focus-visible { background: var(--surface2); outline: none; }
.fx-action.text-danger { color: var(--danger); }

@keyframes fx-sheet-in { from { transform: translateY(24px); opacity: 0.6; } to { transform: none; opacity: 1; } }

/* ---- Plan UX, fase 3: navegación del evento ---- */

/* La barra de pestañas de escritorio se desliza sin enseñar su barra de
   scroll (el degradado de los bordes avisa que hay más). */
.event-nav { scrollbar-width: none; -ms-overflow-style: none; }
.event-nav::-webkit-scrollbar { display: none; }

/* Barra inferior del celular (_event_nav.ejs): respeta el área segura y
   la página le deja su alto libre al final, para que ni el último botón
   ni el pie queden debajo de ella. El hueco va en el <body> y no en el
   <main>: el pie va DESPUÉS del <main>, y con el hueco ahí la barra lo
   tapaba entero al llegar al final. Su alto: los botones (56 px) + el
   borde de arriba + el área segura. Los avisos (toasts) y las barras
   flotantes suben lo mismo. */
.fx-bottom-bar { padding-bottom: env(safe-area-inset-bottom); }
@media (max-width: 767px) {
  body:has([data-event-bottom-bar]) { padding-bottom: calc(env(safe-area-inset-bottom) + 3.5rem + 1px); }
  body:has([data-event-bottom-bar]) .fx-toasts { bottom: calc(env(safe-area-inset-bottom) + 4.75rem); }
  body:has([data-event-bottom-bar]) .fx-float-bottom { bottom: calc(env(safe-area-inset-bottom) + 4.5rem) !important; }
  /* Escribiendo con el teclado del teléfono abierto, la barra se esconde:
     en Android flotaría encima del teclado y del campo. festelia-ui.js
     pone `fx-typing` en <html>. Sin barra no hay nada que librar, y el
     hueco quedaría en blanco debajo del pie. */
  html.fx-typing .fx-bottom-bar { display: none; }
  html.fx-typing body:has([data-event-bottom-bar]) { padding-bottom: 0; }
  html.fx-typing body:has([data-event-bottom-bar]) .fx-toasts { bottom: calc(env(safe-area-inset-bottom) + 1rem); }
}
/* Lo mismo para las barras flotantes ("Cambios sin guardar · Guardar" en
   Datos, fase 4): mientras se escribe estorban encima del teclado, y
   vuelven al salir del campo. */
html.fx-typing .fx-float-bottom { display: none !important; }

/* Barra de pestañas del evento: un degradado en el borde avisa que hay
   más pestañas para deslizar. festelia-ui.js prende `is-start`/`is-end`
   según la posición del scroll (UX-06). */
.event-nav-shell { position: relative; }
.event-nav-shell::before,
.event-nav-shell::after {
  content: '';
  position: absolute;
  top: 0;
  bottom: 1px;
  width: 2.5rem;
  pointer-events: none;
  transition: opacity 150ms ease;
  z-index: 1;
}
.event-nav-shell::before { left: 0; background: linear-gradient(to right, var(--bg), transparent); }
.event-nav-shell::after { right: 0; background: linear-gradient(to left, var(--bg), transparent); }
.event-nav-shell.is-start::before,
.event-nav-shell.is-end::after { opacity: 0; }

/* ---- Consola de administración (layouts/admin.ejs) ----
   Los grupos y sus secciones salen de utils/adminNav.js. Desde `lg`, una
   barra lateral con todos los grupos y el contenido a su lado; debajo de
   `lg` no hay barra lateral: la sección actual va en el encabezado y abre
   una hoja inferior con todas. Clases propias y no de Tailwind porque la
   hoja generada no trae la rejilla ni el `sticky` de `lg`. */
/* El ancho de la consola, el mismo en el encabezado y en el contenido.
   Más que el `max-w-7xl` de antes porque ahora la barra lateral se come
   13rem: con 96rem, la tabla más ancha (Cupones) vuelve a caber completa
   junto a ella en una pantalla de 1536 px o más; en las más chicas se
   desplaza dentro de su caja. Solo los lados: el alto de cada renglón lo
   pone su propio `py-*`. */
.admin-wrap {
  width: 100%;
  max-width: 96rem;
  margin-left: auto;
  margin-right: auto;
  padding-left: 1rem;
  padding-right: 1rem;
}
.admin-shell { flex: 1 0 auto; }
/* `min-width: 0`: sin esto, una tabla ancha ensancha la columna del
   contenido en vez de desplazarse dentro de su caja. */
.admin-main { min-width: 0; padding: 1.5rem 0 2rem; }
@media (min-width: 640px) {
  .admin-main { padding-top: 2rem; }
}
.admin-sidebar { display: none; }
@media (min-width: 1024px) {
  .admin-shell {
    display: grid;
    grid-template-columns: 13rem minmax(0, 1fr);
    column-gap: 2rem;
  }
  /* Fija al bajar por una página larga (la bitácora, un evento), con su
     propio scroll si la ventana es más baja que la lista. */
  .admin-sidebar {
    display: block;
    position: sticky;
    top: 0;
    align-self: start;
    max-height: 100vh;
    overflow-y: auto;
    padding: 2rem 0 1.5rem;
  }
}
.admin-nav-group {
  padding: 0 0.75rem 0.25rem;
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
}
.admin-nav-list { margin-bottom: 1.25rem; }
.admin-nav-link {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  min-height: 2.25rem;
  padding: 0.375rem 0.75rem;
  border-radius: 0.5rem;
  font-size: 0.875rem;
  font-weight: 500;
  color: var(--muted);
}
.admin-nav-link:hover { background: var(--surface2); color: var(--ink); }
/* La sección activa: blanca y con la raya ámbar de la leyenda de la
   consola, para que se note sin depender solo del color del texto. */
.admin-nav-link[aria-current="page"] {
  background: var(--surface);
  color: var(--ink);
  font-weight: 600;
  box-shadow: inset 3px 0 0 rgb(251 191 36), 0 1px 2px rgb(15 23 42 / 0.06);
}
/* El foco del teclado: ámbar oscuro sobre el fondo claro, por dentro del
   renglón porque la barra lateral recorta lo que sale de ella; ámbar claro
   sobre el encabezado oscuro. */
.admin-nav-link:focus-visible { outline: 2px solid var(--warn); outline-offset: -2px; }
.admin-menu-button:focus-visible { outline: 2px solid rgb(251 191 36); outline-offset: 2px; }

/* El botón del encabezado en el celular: todo el renglón se toca. */
.admin-menu-button {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  width: 100%;
  min-height: 44px;
  font-size: 0.875rem;
  text-align: left;
}
/* La hoja del menú: los cuatro grupos en dos columnas, para que las
   secciones quepan sin deslizar en un teléfono. */
.admin-menu-groups {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.75rem 0.5rem;
}
.admin-menu .admin-nav-group { padding: 0.25rem 0.625rem 0.25rem; }
.admin-menu .fx-action { min-height: 44px; padding: 0.375rem 0.625rem; font-size: 0.875rem; line-height: 1.25; }
.admin-menu .fx-action[aria-current="page"] {
  background: var(--warn-soft);
  color: var(--ink);
  font-weight: 600;
  box-shadow: inset 3px 0 0 rgb(251 191 36);
}

/* Lo que ensanchaba las páginas de la consola en el celular, arreglado
   una vez aquí y no página por página:
   - Tablas: las cajas que las recortan (`overflow-hidden`, por las
     esquinas redondeadas) ahora las desplazan de lado dentro de la caja,
     en vez de cortar las columnas de la derecha.
   - Rejillas: una tabla o un <pre> con su propio scroll ensanchaba toda
     la columna de la rejilla (y la página) hasta su ancho completo.
   - Un <select> con una opción larga (una campaña del Embudo) medía lo
     que su opción más larga, y su etiqueta con él.
   - Correos, ligas y detalles sin espacios: se parten donde haga falta.
     `anywhere` solo en textos (párrafos, valores de una lista, títulos,
     los usuarios elegidos de un cupón), no en botones ni etiquetas, que no
     deben partirse a media palabra; ni dentro de las tablas, que se
     desplazan. */
.admin-main .overflow-hidden:has(> table) { overflow-x: auto; }
.admin-main .grid > * { min-width: 0; }
.admin-main label,
.admin-main select { max-width: 100%; }
.admin-main { overflow-wrap: break-word; }
.admin-main :is(p, dd, h1, h2, h3, li > span) { overflow-wrap: anywhere; }
.admin-main table :is(p, dd, h1, h2, h3, li > span) { overflow-wrap: normal; }
/* Un renglón que puede partirse (`flex-wrap`) con una columna de texto
   flexible (`min-w-0 flex-1`), como los de la Bitácora: en el celular la
   etiqueta y los botones se comían el ancho y el texto quedaba en una
   columna de unas cuantas letras. Con este mínimo, lo que no cabe baja al
   siguiente renglón. */
.admin-main .flex-wrap > .min-w-0.flex-1 { min-width: 10rem; }

@media print {
  .admin-sidebar { display: none !important; }
  .admin-shell.admin-wrap { display: block; max-width: none; padding: 0; }
}
