/* Montserrat, tipografía base de Sin Taquilla.
 *
 * Se sirve desde el propio dominio y no desde Google Fonts a propósito: la CSP de la
 * aplicación es `default-src 'self'`, y como no declara `font-src`, hereda ese valor.
 * Una hoja o un archivo de fuente alojado en fonts.googleapis.com quedaría bloqueado por
 * el navegador. Alojarla aquí además evita una petición a un tercero desde páginas que
 * muestran datos de compra.
 *
 * Un solo archivo variable cubre todos los pesos. Ver assets/fonts/README.md.
 */
@font-face {
    font-family: "Montserrat";
    src: url("../fonts/montserrat-latin.woff2") format("woff2-variations");
    font-weight: 100 900;
    font-style: normal;
    font-display: swap;
}
:root {
    --navy: #0b1c30;
    --accent: #0058be;
    --accent-ink: #ffffff;
    --accent-soft: rgba(0, 88, 190, .12);
    --accent-ring: rgba(0, 88, 190, .24);
    --accent-veil: rgba(0, 88, 190, .18);
    --accent-light: #8ebfff;
    --paper: #ffffff;
    --surface: #f8f9ff;
    --line: #e6e8ef;
    --muted: #787680;
    --green: #10b981;
    --red: #ef4444;
    --amber: #f59e0b;
    /* Los semánticos de arriba están pensados como fondo. Estos son sus versiones para texto
       pequeño sobre blanco, que necesitan bastante más oscuridad: medido, el ámbar #f59e0b se
       queda en 2.15:1 y no se lee. Estos cuatro pasan de 4.5:1 —5.48, 4.92, 5.18 y 6.47—. */
    --green-ink: #047857;
    --amber-ink: #a16207;
    --orange-ink: #c2410c;
    --red-ink: #b91c1c;
    --shadow: 0 14px 40px rgba(11, 28, 48, .08);
    /* Los grises del esqueleto de carga. No salen de `--line` ni de `--surface`: entre esos
       dos hay dos puntos de diferencia y el brillo no se vería. Estos son neutros a propósito,
       porque el esqueleto no debe teñirse con el acento que cada quien elija. */
    --skeleton: #e7eaf2;
    --skeleton-sheen: #f4f6fb;
}

* { box-sizing: border-box; }
[hidden] { display: none !important; }
html { min-width: 320px; background: var(--surface); }
/* Acento elegido por cada persona en «Mi cuenta».
 *
 * Sólo cambia el color de acción: los semánticos —verde de éxito, ámbar de aviso, rojo de
 * error— se quedan fijos en los ocho. Un error debe verse rojo aunque alguien haya elegido
 * el acento verde.
 *
 * Cada uno llega a 4.5:1 —el mínimo para texto normal— con su propia `--accent-ink`, porque el
 * acento se usa como fondo de botones y de iconos. Siete heredan la tinta blanca de `:root`; el
 * naranja zanahoria trae la suya casi negra, y por eso la tinta es una variable y no un literal. */
html[data-acento="verde"] {
    --accent: #0b7a53;
    --accent-soft: rgba(11, 122, 83, .12);
    --accent-ring: rgba(11, 122, 83, .24);
    --accent-veil: rgba(11, 122, 83, .18);
    --accent-light: #6fd3a8;
}
html[data-acento="violeta"] {
    --accent: #5b34c7;
    --accent-soft: rgba(91, 52, 199, .12);
    --accent-ring: rgba(91, 52, 199, .24);
    --accent-veil: rgba(91, 52, 199, .18);
    --accent-light: #b9a3ff;
}
html[data-acento="grafito"] {
    --accent: #334155;
    --accent-soft: rgba(51, 65, 85, .12);
    --accent-ring: rgba(51, 65, 85, .24);
    --accent-veil: rgba(51, 65, 85, .18);
    --accent-light: #a9b6c9;
}
html[data-acento="vino"] {
    --accent: #722f37;
    --accent-soft: rgba(114, 47, 55, .12);
    --accent-ring: rgba(114, 47, 55, .24);
    --accent-veil: rgba(114, 47, 55, .18);
    --accent-light: #e0a3ac;
}
html[data-acento="cafe"] {
    --accent: #4a2c1a;
    --accent-soft: rgba(74, 44, 26, .12);
    --accent-ring: rgba(74, 44, 26, .24);
    --accent-veil: rgba(74, 44, 26, .18);
    --accent-light: #cdaa8b;
}
html[data-acento="rosa"] {
    --accent: #e4007c;
    --accent-soft: rgba(228, 0, 124, .12);
    --accent-ring: rgba(228, 0, 124, .24);
    --accent-veil: rgba(228, 0, 124, .18);
    --accent-light: #ff9ecb;
}
/* El único que no lleva texto blanco encima.
   El naranja zanahoria de verdad —#ed7014— solo alcanza 3.03:1 con blanco, muy por debajo del
   mínimo. Oscurecerlo hasta pasar deja de ser naranja zanahoria: hay que llegar a #a34a05, que ya
   es un naranja quemado. La salida es la contraria: el color se respeta y lo que cambia es la
   tinta que va encima, que aquí es casi negra y da 5.78:1. */
html[data-acento="naranja"] {
    --accent: #ed7014;
    --accent-ink: #2a1400;
    --accent-soft: rgba(237, 112, 20, .14);
    --accent-ring: rgba(237, 112, 20, .28);
    --accent-veil: rgba(237, 112, 20, .20);
    --accent-light: #ffc79a;
}

/* Tamaño de texto elegido por cada persona en «Mi cuenta».
 *
 * Toda la escala tipográfica de esta hoja está en `rem`, así que basta con mover la raíz para
 * que texto, botones y espaciados crezcan juntos y en proporción.
 *
 * En porcentaje y no en píxeles a propósito: quien ya configuró una letra grande en su navegador
 * la conserva, y estas opciones escalan a partir de ahí. Fijar un número de píxeles anularía esa
 * preferencia del sistema, que es justo lo contrario de lo que busca esta función.
 *
 * `normal` no lleva regla: es el tamaño base del navegador, sin tocar. */
html[data-texto="compacto"] { font-size: 93.75%; }
html[data-texto="grande"] { font-size: 112.5%; }
html[data-texto="mayor"] { font-size: 125%; }
body { margin: 0; overflow-x: hidden; color: var(--navy); background: var(--surface); font-family: "Montserrat", ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; font-weight: 300; }
button, input, select, textarea { font: inherit; }
button { cursor: pointer; }
button:focus-visible, a:focus-visible, input:focus-visible, select:focus-visible, textarea:focus-visible { outline: 3px solid var(--accent-ring); outline-offset: 2px; }
h1, h2, h3, h4 { font-weight: 500; }
h1, h2, h3, p { margin-top: 0; }
h1 { margin-bottom: 0; font-size: clamp(1.65rem, 3vw, 2.25rem); letter-spacing: -.04em; }
h2 { margin: .25rem 0 0; font-size: 1.05rem; }

.login-page { min-height: 100vh; display: grid; place-items: center; padding: 24px; background: radial-gradient(circle at 20% 0%, #dfeaff, transparent 36%), var(--surface); }
.login-card { display: grid; grid-template-columns: 1.1fr .9fr; width: min(920px, 100%); overflow: hidden; background: var(--paper); border: 1px solid var(--line); border-radius: 28px; box-shadow: var(--shadow); }
.login-brand { min-height: 430px; padding: 56px; display: flex; flex-direction: column; justify-content: end; color: white; background: linear-gradient(150deg, #071321, #0b1c30 65%, var(--accent)); }
.login-brand h1 { margin: 12px 0; font-size: clamp(2.4rem, 7vw, 4.4rem); }
.login-brand p { max-width: 420px; color: #cbd5e1; }
.brand-kicker, .eyebrow { font-size: .68rem; font-weight: 600; letter-spacing: .16em; text-transform: uppercase; color: var(--accent); }
.login-brand .brand-kicker { color: var(--accent-light); }
.login-form { padding: 56px; display: flex; flex-direction: column; justify-content: center; gap: 18px; }
.auth-form { display: grid; gap: 16px; }
.auth-link { color: var(--accent); font-size: .74rem; font-weight: 500; text-decoration: none; }
.auth-link:hover { text-decoration: underline; }
.password-help { margin: 0; color: var(--muted); font-size: .68rem; line-height: 1.5; }

label { display: grid; gap: 7px; color: #344054; font-size: .76rem; font-weight: 500; }
input, select, textarea { width: 100%; padding: 11px 13px; color: var(--navy); background: white; border: 1px solid #d9dde7; border-radius: 10px; outline: none; transition: border-color .15s, box-shadow .15s; }
input:focus, select:focus, textarea:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
input:disabled { background: #f1f3f7; color: var(--muted); }
textarea { resize: vertical; }

.button { display: inline-flex; align-items: center; justify-content: center; min-height: 40px; padding: 9px 16px; border: 1px solid transparent; border-radius: 10px; font-size: .78rem; font-weight: 500; text-decoration: none; transition: transform .15s, background .15s; }
.button:hover { transform: translateY(-1px); }
.button-primary { color: var(--accent-ink); background: var(--accent); }
/* Botón con un icono delante del texto. El icono se pinta con `currentColor`, así que toma el color
   del texto del botón: sobre el acento de «Mi cuenta», el de contraste que lo acompaña. */
.button-with-icon { gap: 8px; }
.button-with-icon svg { width: 18px; height: 18px; flex: 0 0 auto; }
/* Aquí vivía `.button-dark`, un negro fijo que competía con `.button-primary` sin significar
   nada distinto: en la misma pantalla convivían «Guardar remitente» azul y «Guardar aplicación»
   negro. Se retiró y sus usos pasaron a `.button-primary`, que sigue el acento elegido.

   La única excepción era el «Cerrar» del diálogo de comprobante, que usaba el estilo más
   llamativo de la aplicación para descartar un diálogo. Ese pasó a `.button-light`, que es lo
   que le corresponde: pintarlo del color de acción lo habría convertido en el botón principal
   por encima de «Imprimir». */
.button-light { color: var(--navy); background: white; border-color: var(--line); }
.button-danger { color: var(--red); background: #fff5f5; border-color: #fee2e2; }
.button-danger-solid { color: white; background: #c81e1e; border-color: #c81e1e; }
.button:disabled { cursor: not-allowed; opacity: .48; transform: none; }

.topbar { height: 40px; padding: 0 28px; display: flex; align-items: center; justify-content: space-between; color: white; background: #000; font-size: .75rem; letter-spacing: .08em; text-transform: uppercase; }
/* Hijo directo, no cualquier `span`: dentro de la pildora vive el punto del led, y con el selector
   suelto heredaba el relleno y el fondo de la pildora y se dibujaba como una segunda capsula. */
.topbar > span { padding: 4px 9px; display: inline-flex; align-items: center; gap: 7px; background: rgba(255,255,255,.15); border-radius: 999px; font-size: .58rem; }
/* El halo es lo que lo convierte en un led y no en un punto: un anillo del mismo verde, muy
   diluido, como el resplandor de un diodo sobre una carcasa negra. */
.session-led { width: 7px; height: 7px; flex: 0 0 auto; background: var(--green); border-radius: 50%; box-shadow: 0 0 0 2px rgba(16, 185, 129, .3); }
.workspace { min-height: calc(100vh - 40px); display: grid; grid-template-columns: 245px minmax(0, 1fr); }
.sidebar { padding: 24px 16px; display: flex; flex-direction: column; color: white; background: var(--navy); }
.organization { margin-bottom: 28px; padding: 0 8px; display: flex; align-items: center; gap: 10px; min-width: 0; }
.organization-mark { width: 38px; height: 38px; display: grid; place-items: center; flex: 0 0 auto; color: var(--navy); background: white; border-radius: 11px; font-weight: 600; }
.organization div { min-width: 0; }
.organization strong, .organization small { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.organization strong { font-size: .78rem; }
.organization small { margin-top: 3px; color: #91a4bb; font-size: .58rem; text-transform: uppercase; }
.sidebar nav { display: grid; gap: 7px; }
.nav-item { width: 100%; padding: 11px 13px; display: flex; align-items: center; gap: 10px; color: #b9c6d5; background: transparent; border: 0; border-radius: 10px; text-align: left; font-size: .78rem; font-weight: 500; }
.nav-item:hover, .nav-item.is-active { color: white; background: rgba(255,255,255,.1); }
.nav-item.is-active { box-shadow: inset 3px 0 var(--accent); }
.nav-icon { width: 20px; height: 20px; display: block; flex: 0 0 auto; }
.nav-icon svg { width: 100%; height: 100%; display: block; }
.mobile-account, .mobile-logout { display: none; }
/* Cierra la lista del menú. El margen replica el hueco entre entradas, porque vive fuera de la
   rejilla de `nav` y sin él quedaría pegado a la última. */
.logout { width: 100%; margin-top: 7px; padding: 12px 13px; display: flex; align-items: center; gap: 10px; border-radius: 10px; color: #aebdcd; text-decoration: none; font-size: .72rem; font-weight: 500; transition: color .15s, background .15s; }
/* Único elemento del bloque inferior: `margin-top: auto` lo empuja al fondo de la barra. */
.sidebar-toggle { width: 100%; margin-top: auto; padding: 11px 13px; display: flex; align-items: center; gap: 10px; color: #8ea2b8; background: transparent; border: 0; border-radius: 10px; text-align: left; font-size: .72rem; font-weight: 500; transition: color .15s, background .15s; }
.sidebar-toggle:hover { color: white; background: rgba(255,255,255,.08); }
.sidebar-toggle .nav-icon { transition: transform .18s; }
.logout:hover, .logout:focus-visible { color: white; background: rgba(255,255,255,.1); }

/* El hueco de la barra se reserva siempre. Esta columna tiene su propio desplazamiento, y su
   barra aparece o desaparece según lo alto que sea el contenido: al plegar las secciones de
   Configuración desaparecía y la columna entera se ensanchaba quince píxeles, moviendo con ella
   la flecha de plegado que debe quedarse quieta. */
.main-content { min-width: 0; padding: 0 28px 40px; overflow: auto; scrollbar-gutter: stable; }
/* El alto sale del contenido mas su relleno, en vez de un minimo fijo.
 *
 * Medido en el navegador: de los 104 px que reservaba, 60 eran contenido —la linea del sobretitulo
 * ocupaba 20 y el titulo 40— y los otros 44 eran aire, 22 arriba y 22 abajo. Ese aire hace falta:
 * `.main-content` no tiene relleno superior propio, asi que este encabezado hacia tambien de margen
 * de la pagina y sin el el titulo quedaria pegado al filo.
 *
 * Conservando los 22 y quitando la linea, el encabezado pasa de 104 a 84 px: todo lo que hay debajo
 * sube exactamente lo que ocupaba el sobretitulo, ni un pixel mas. */
.page-header { padding: 22px 0; display: flex; align-items: center; justify-content: space-between; gap: 20px; }
.user-chip { display: flex; align-items: center; gap: 10px; }
.user-chip > span { width: 36px; height: 36px; display: grid; place-items: center; color: white; background: var(--navy); border-radius: 50%; font-weight: 600; }
.user-chip strong, .user-chip small { display: block; }
.user-chip strong { font-size: .72rem; }
.user-chip small { margin-top: 2px; color: var(--muted); font-size: .62rem; }
.view { display: none; animation: enter .18s ease-out; }
.view.is-active { display: block; }
@keyframes enter { from { opacity: 0; transform: translateY(6px); } }

/* Esqueletos de carga.
 *
 * El brillo que los recorre es un degradado moviéndose con `background-position`, no un
 * elemento deslizándose: así el navegador no rehace el diseño en cada fotograma, y con veinte
 * barras en pantalla eso se nota.
 *
 * `display: contents` en el envoltorio es lo que permite que el esqueleto viva dentro de una
 * rejilla ajena —`.stats-grid`, `.event-grid`— sin ser él mismo una celda: sus hijos ocupan su
 * lugar. El envoltorio existe solo para que `app.js` sepa qué quitar si la petición falla.
 *
 * El marcado lo escribe el servidor; ver `loading_skeleton()`.
 */
.skeleton-block { display: contents; }
.skeleton { display: block; height: 11px; border-radius: 7px; background: linear-gradient(90deg, var(--skeleton) 8%, var(--skeleton-sheen) 20%, var(--skeleton) 32%); background-size: 500% 100%; animation: skeleton-sweep 1.5s ease-in-out infinite; }
@keyframes skeleton-sweep { from { background-position: 100% 0; } to { background-position: 0 0; } }
/* Las medidas de abajo no son estéticas: son las del contenido real, medidas en el navegador.
   Un esqueleto más bajo que lo que anuncia empuja media pantalla hacia abajo en cuanto llega el
   dato, y ese salto es justo lo que se quería evitar. */
.skeleton-label { width: 42%; height: 9px; margin-top: 3px; }
.skeleton-value { width: 58%; height: 33px; margin-top: 16px; border-radius: 9px; }
.skeleton-chart { height: 200px; border-radius: 12px; }
.skeleton-cover { height: 132px; border-radius: 0; }
.skeleton-title { width: 66%; height: 14px; }
.skeleton-line-short { width: 45%; }
/* La ficha de evento tiene debajo del texto una fila de cifras y otra de botones. Sin ellas el
   esqueleto se quedaba 97 px por debajo de la tarjeta real —medido— y la rejilla daba un tirón
   hacia abajo al cargar. */
.skeleton-meta { height: 34px; border-radius: 9px; }
.skeleton-actions { height: 40px; border-radius: 11px; }
.event-body > .skeleton + .skeleton { margin-top: 11px; }
/* Cinco columnas de anchos desiguales: una tabla real tampoco los tiene iguales, y en gris la
   regularidad se lee como una cuadrícula vacía en lugar de como datos. La altura de la fila y la
   del encabezado son las de una tabla de verdad, con su misma línea divisoria. */
.skeleton-row { height: 57px; display: grid; grid-template-columns: 2.1fr 1.5fr .7fr .9fr 1.2fr; gap: 20px; align-items: center; border-bottom: 1px solid #edf0f5; }
.skeleton-row-head { height: 38px; }
.skeleton-row-head .skeleton { height: 7px; opacity: .7; }

/* La barra de progreso de la parte de arriba.
 *
 * Indeterminada a propósito: la aplicación no sabe cuánto va a tardar el servidor, y una barra
 * que finge saberlo llega al 90 % y se queda ahí. Esta solo dice «hay algo en camino».
 * Se desliza con `transform`, que el navegador resuelve sin tocar el diseño.
 *
 * Por debajo del lector de códigos a pantalla completa (`z-index: 1000`) y por encima de todo
 * lo demás. Los diálogos no compiten: `showModal()` los pone en la capa superior del navegador.
 */
.app-progress { position: fixed; top: 0; right: 0; left: 0; z-index: 900; height: 2px; overflow: hidden; opacity: 0; transition: opacity .18s ease-out; pointer-events: none; }
.app-progress.is-active { opacity: 1; }
.app-progress span { width: 38%; height: 100%; display: block; background: linear-gradient(90deg, transparent, var(--accent), transparent); animation: app-progress 1.15s ease-in-out infinite; }
@keyframes app-progress { from { transform: translateX(-100%); } to { transform: translateX(365%); } }

/* Entrada escalonada de las tarjetas.
 *
 * Solo tarjetas, nunca filas de tabla: el historial de órdenes se vuelve a dibujar con cada
 * letra que se teclea en su buscador, y animar las filas convertiría la búsqueda en un
 * parpadeo continuo.
 *
 * Los retrasos van enumerados porque CSS no sabe multiplicar por la posición del hijo. A partir
 * de la octava tarjeta todas comparten el último retraso: escalonar más allá de un tercio de
 * segundo deja de leerse como una entrada y empieza a leerse como lentitud.
 */
.stats-grid > .stat-card, .event-grid > .event-card, .event-grid > .event-create-card { animation: enter .24s ease-out both; }
.stats-grid > :nth-child(2), .event-grid > :nth-child(2) { animation-delay: .04s; }
.stats-grid > :nth-child(3), .event-grid > :nth-child(3) { animation-delay: .08s; }
.stats-grid > :nth-child(4), .event-grid > :nth-child(4) { animation-delay: .12s; }
.stats-grid > :nth-child(5), .event-grid > :nth-child(5) { animation-delay: .16s; }
.stats-grid > :nth-child(6), .event-grid > :nth-child(6) { animation-delay: .2s; }
.stats-grid > :nth-child(7), .event-grid > :nth-child(7) { animation-delay: .24s; }
.stats-grid > :nth-child(n+8), .event-grid > :nth-child(n+8) { animation-delay: .28s; }

/* Quien pidió menos movimiento conserva la información y pierde la animación: el esqueleto se
 * queda gris y quieto, la barra de progreso se queda encendida de lado a lado, y las tarjetas
 * aparecen sin desplazarse. Ninguno de los tres deja de comunicar lo que comunicaba. */
@media (prefers-reduced-motion: reduce) {
    .view, .stats-grid > .stat-card, .event-grid > .event-card, .event-grid > .event-create-card { animation: none; }
    .skeleton { background: var(--skeleton); animation: none; }
    .app-progress span { width: 100%; background: var(--accent); animation: none; }
}

.stats-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 16px; margin-bottom: 18px; }
.stat-card, .panel, .event-card { background: var(--paper); border: 1px solid var(--line); border-radius: 16px; box-shadow: 0 5px 18px rgba(11, 28, 48, .035); }
.stat-card { padding: 20px; }
.stat-card small { display: block; color: var(--muted); font-size: .64rem; font-weight: 600; letter-spacing: .09em; text-transform: uppercase; }
.stat-card strong { display: block; margin-top: 12px; font-size: 1.7rem; letter-spacing: -.04em; }
.panel { padding: 20px; }
.panel-header, .view-toolbar { display: flex; align-items: center; justify-content: space-between; gap: 16px; }
.panel-header { margin-bottom: 18px; }
.view-toolbar { margin-bottom: 18px; }
.view-toolbar p { margin: 0; color: var(--muted); font-size: .8rem; }
/* El cupo del plan. Discreto a propósito: es una referencia que se consulta, no un aviso. Quien
   choca con el tope recibe además un error al guardar, que sí interrumpe. */
.plan-usage { margin: -6px 0 16px; padding: 13px; color: var(--muted); background: var(--surface); border: 1px solid var(--line); border-radius: 10px; font-size: .72rem; }
.plan-usage-heading { color: var(--navy); font-weight: 700; margin-bottom: 11px; }
.plan-usage-details { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 9px; }
.plan-usage-card { min-width: 0; padding: 10px; border: 1px solid var(--line); border-radius: 9px; background: var(--paper); display: grid; gap: 4px; }
.plan-usage-card span { color: var(--muted); }
.plan-usage-card strong { color: var(--navy); font-size: .96rem; }
.plan-usage-card small { color: var(--muted); line-height: 1.4; }
.plan-usage-card.is-exhausted strong, .plan-usage-card.is-exhausted small { color: var(--amber-ink); }
.plan-usage-help { margin: 10px 0 0; line-height: 1.5; }
@media (max-width: 900px) { .plan-usage-details { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 420px) { .plan-usage-details { grid-template-columns: 1fr; } }
/* Cuando el plan ya no deja publicar un evento nuevo —venció, o se usó su cupo—, el indicador pasa de
   referencia discreta a aviso: es lo primero que ve quien entra a Eventos. */
.plan-usage.is-blocked { color: var(--amber-ink); background: #fffbeb; border-color: #f5d68a; }
.event-plan-notice { margin: 0 0 16px; }
/* Un plan vencido en rojo, en la tabla y en el detalle: es lo único de esa columna que pide una
   llamada hoy, y se lee en diagonal entre doscientas filas. */
.plan-alert { color: var(--red); font-weight: 600; }
/* El detalle de una organización: pares de dato y valor en dos columnas, que colapsan a una en el
   teléfono. Es una lista de consulta, no un formulario. */
.detail-list { margin: 0 0 18px; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px 20px; }
.detail-list > div { min-width: 0; }
.detail-list .detail-wide { grid-column: 1 / -1; }
.detail-list dt { margin-bottom: 2px; color: var(--muted); font-size: .64rem; text-transform: uppercase; letter-spacing: .06em; }
.detail-list dd { margin: 0; overflow-wrap: anywhere; font-size: .8rem; }
.detail-list dd small { display: block; color: var(--muted); font-size: .68rem; }
/* El restablecimiento de toda la organización, separado de la lista de eventos y con el borde de la
   zona de peligro: es la operación que más borra y no debe confundirse con una fila más. */
.support-factory { margin-top: 18px; padding: 14px 16px; display: flex; align-items: center; justify-content: space-between; gap: 16px; border: 1px solid rgba(239, 68, 68, .35); border-radius: 14px; }
.support-factory h3 { margin: 0 0 4px; font-size: .9rem; }
.support-factory p { margin: 0; color: var(--muted); font-size: .76rem; }
.table-wrap { overflow-x: auto; }
table { width: 100%; border-collapse: collapse; }
th, td { padding: 12px 10px; border-bottom: 1px solid #edf0f5; text-align: left; font-size: .72rem; }
th { color: var(--muted); font-size: .6rem; letter-spacing: .08em; text-transform: uppercase; }
td strong, td small { display: block; }
td small { margin-top: 3px; color: var(--muted); }
.empty { padding: 42px 18px; color: var(--muted); text-align: center; font-size: .78rem; }

/* --- Tablas de columnas ajustables -------------------------------------------------------
   `table-layout: fixed` es lo que hace que un ancho escrito se respete tal cual; con el
   reparto automatico el navegador reinterpreta cada valor segun el contenido y arrastrar el
   borde se siente elastico. A cambio, el texto que no cabe hay que recortarlo: por eso el
   `text-overflow` va aqui y no como decoracion.

   Los anchos por omision se declaran en porcentaje para que la tabla llene el contenedor sin
   desbordarlo. Sin ellos, el reparto fijo daria a las seis columnas el mismo ancho y «Fecha»
   ocuparia tanto como «Detalle». En cuanto alguien arrastra, JavaScript los sustituye por
   pixeles; la CSP de la aplicacion prohibe el `style=` en el marcado, asi que esos pixeles se
   escriben por CSSOM sobre cada <col>, que es la unica via que el navegador aplica. */
.table-resizable table { table-layout: fixed; width: 100%; min-width: 100%; }
.table-resizable td { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
.table-resizable td strong, .table-resizable td small, .table-resizable td code {
    display: block; overflow: hidden; white-space: nowrap; text-overflow: ellipsis;
}
/* El encabezado no recorta con `overflow` propio: el tirador es hijo suyo y quedaria cortado.
   Recorta la etiqueta, que es quien tiene texto. */
.table-resizable th { position: relative; overflow: visible; }
.table-resizable .th-label { display: block; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
/* El tirador queda dentro de la celda. Montarlo a caballo del borde —`right: -5px`— se lee mejor
   pero deja al de la última columna sobresaliendo de la tabla, y esos pixeles cuentan como
   desbordamiento: el aviso de «desliza para ver más» aparecía sobre una tabla que cabía entera. */
.col-resizer {
    position: absolute; top: 0; right: 0; z-index: 1;
    width: 10px; height: 100%; cursor: col-resize; touch-action: none;
    background: none; border: 0; padding: 0;
}
.col-resizer::after {
    content: ''; position: absolute; top: 25%; right: 3px;
    width: 2px; height: 50%; background: transparent; border-radius: 1px;
}
.col-resizer:hover::after, .col-resizer:focus-visible::after { background: var(--accent); }
.col-resizer:focus-visible { outline: none; }
.table-resizable.is-resizing { cursor: col-resize; user-select: none; }

/* Sin cursor preciso no hay borde que agarrar, y un tirador de diez pixeles solo estorbaria
   al desplazar la tabla con el dedo. */
@media (pointer: coarse) {
    .col-resizer { display: none; }
}

/* Aviso de desplazamiento gobernado por el desbordamiento real y no por el ancho de pantalla.
   Lo muestra y lo oculta JavaScript con el atributo `hidden`. */
.scroll-hint { margin: -4px 0 10px; color: var(--muted); font-size: .65rem; text-align: right; }

.event-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 17px; }
.event-card { overflow: hidden; }
.event-create-card { display: none; }
/* El cartel entero, sobre su propia copia borrosa.
 *
 * Un cartel de evento suele ser vertical, y recortarlo a una portada apaisada se llevaba por
 * delante casi todo: medido, de un cartel 2:3 solo se veia el **29 %**, y ademas la franja central,
 * que es justo donde no esta ni el titulo ni la fecha. Con `contain` se ve completo, y el hueco que
 * queda a los lados lo llena el mismo cartel desenfocado en vez de una franja de color plano.
 *
 * Es el tratamiento que ya usa la pagina publica de venta desde hace meses; aqui solo se copia.
 * El `scale` esconde el borde que el desenfoque deja translucido en las orillas.
 */
.event-cover { height: 132px; position: relative; overflow: hidden; background: linear-gradient(135deg, var(--navy), var(--accent)); background-size: cover; background-position: center; }
.event-cover-backdrop { width: 100%; height: 100%; position: absolute; inset: 0; z-index: 0; display: block; object-fit: cover; filter: blur(13px) brightness(.78) saturate(.9); transform: scale(1.14); }
.event-cover-image { width: 100%; height: 100%; position: relative; z-index: 1; display: block; object-fit: contain; }
.event-cover .status { position: absolute; top: 12px; right: 12px; }
.event-body { padding: 17px; }
.event-body h3 { margin-bottom: 7px; font-size: .96rem; }
.event-body p { margin-bottom: 13px; color: var(--muted); font-size: .7rem; line-height: 1.5; }
.event-meta { display: grid; grid-template-columns: 1fr 1fr; gap: 9px; padding-top: 12px; border-top: 1px solid var(--line); font-size: .68rem; }
.event-actions { margin-top: 14px; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; }
.event-actions .button { width: 100%; min-width: 0; white-space: normal; text-align: center; }
.status { display: inline-flex; padding: 4px 8px; border-radius: 999px; color: #036b49; background: #dff8ee; font-size: .56rem; font-weight: 600; text-transform: uppercase; }
.status-draft { color: #7b5b00; background: #fff5ce; }
.status-warning { color: #8a4b00; background: #ffe6b3; }
.status-past, .status-archived { color: #5f6670; background: #e8ebef; }
.status-disabled { color: #5f6670; background: #e8ebef; }
.status-sold_out { color: #a11b1b; background: #fee2e2; }
.status-pending { color: #7b5b00; background: #fff3c4; }
.status-expired { color: #5f6670; background: #e8ebef; }
.status-cancelled { color: #a11b1b; background: #fee2e2; }
.status-refunded { color: #8a3d00; background: #ffead5; }
.status-partially_refunded { color: #8a3d00; background: #ffead5; }
.status-disputed { color: #6b2ca0; background: #f0e2ff; }
/* Los dos estados de boleto que salen a diario en el padrón. `issued` fija el verde que hasta
   ahora heredaba del `.status` base: así no cambia sin querer el día que se retoque ese verde.

   `used` va en amarillo y no en rojo a propósito. El rojo de esta paleta significa «esto salió
   mal» —cancelado, agotado—, y un boleto usado es justo lo contrario: alguien que entró como
   debía. En rojo se leería igual que «Cancelado», que aparece en esa misma columna. El amarillo
   es el del borrador, un estado que en el padrón no existe, así que no hay dos amarillos que
   confundir. */
.status-issued { color: #036b49; background: #dff8ee; }
.status-used { color: #7b5b00; background: #fff5ce; }

.two-columns { display: grid; grid-template-columns: minmax(330px, .8fr) minmax(0, 1.2fr); gap: 18px; align-items: start; }
/* La columna de captura, estrecha a propósito.
 *
 * Dentro del formulario cada campo ocupa el ancho entero de esta columna, sin topes campo por campo:
 * todos terminan en la misma línea. Eso obliga a que la columna mida poco. Un campo tan ancho como el
 * panel —930 px— hace recorrer con la vista de la etiqueta al final de la caja, y una caja de ese
 * tamaño para diez dígitos de teléfono no dice lo que se espera dentro: el ancho de un campo es la
 * primera pista de cuánto se escribe en él.
 *
 * Y nunca más de la mitad: con el menú abierto en una pantalla justa, fijar 560 px dejaba la ficha del
 * evento en 140 px. `min()` vuelve al reparto a mitades cuando los dos no caben.
 *
 * `.two-columns` solo se usa aquí, pero el reparto se escribe en la clase de la vista para que se vea
 * a qué pantalla pertenece. */
.sales-layout { grid-template-columns: minmax(0, min(560px, 50%)) minmax(0, 1fr); }
.sales-layout, .sales-layout > * { min-width: 0; }
.form-panel { display: grid; gap: 16px; }
.form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
/* Cada etiqueta es a su vez una rejilla —texto, campo, ayuda— y se estira a la altura de su
   fila. Sin esto, el reparto por omisión da ese sobrante a las filas internas y **engorda el
   campo**: en dos columnas contiguas con textos de ayuda de distinta longitud, la que tiene la
   ayuda más corta acababa con la caja cinco píxeles más alta y cinco más baja que su vecina.
   Con `start`, el sobrante se queda abajo y los campos quedan a la misma altura.

   Es `align-content` y no `align-self` a propósito: `align-self` en este selector ganaría por
   especificidad al `align-self: end` de `.checkbox-label` y descolocaría todas las casillas.
   `align-content` es inerte en un contenedor flexible de una sola línea, así que las respeta. */
.form-grid > label { align-content: start; }
.span-2 { grid-column: 1 / -1; }
.checkbox-label { display: flex; align-items: center; align-self: end; min-height: 42px; }
.checkbox-label input { width: auto; }
.payment-selector-field { align-self: start; }
/* Tres dígitos y sus flechas. El máximo por orden no pasa de 100, y ocupando la celda entera
   —media rejilla en el diálogo, un tercio en la pantalla completa— parecía pedir un número largo.
   Sirve en los dos sitios porque el formulario es el mismo y se muda entre ellos. */
#event-form input[name="max_tickets_per_order"] { width: min(120px, 100%); }
/* El contenedor del máximo por orden hace en escritorio lo que antes hacía su `<label>`: texto
   arriba, campo abajo. Copia también su tipografía, y no por estética: los campos llevan
   `font: inherit`, así que el número heredaba el tamaño de la etiqueta; al salir de ella sin
   esto crecería y el campo quedaría más alto que antes. Los botones no existen aquí. */
.order-max-field { display: grid; gap: 7px; color: #344054; font-size: .76rem; font-weight: 500; }
.order-max-stepper button { display: none; }
/* El campo y las dos casillas, en una fila que se empaqueta de izquierda a derecha.
 *
 * La rejilla del formulario reparte columnas iguales, y eso es lo correcto para los campos que se
 * llenan escribiendo. Aquí no: un campo de tres dígitos y dos casillas ocupan lo que mide su texto,
 * y con celdas de 377 px quedaban separados por huecos de más de 200. Se envuelve sola, así que en
 * el diálogo angosto y en el teléfono lo que no cabe baja al renglón siguiente.
 *
 * `flex-end` alinea las casillas con la base del campo, que es donde estaban con `align-self: end`
 * cuando cada una vivía en su propia celda. */
.event-options-row { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 12px 28px; }
.event-options-row > label { margin: 0; }
/* La ayuda y el aviso de la casilla son párrafos dentro de la rejilla: el hueco de la rejilla ya
   los separa, y el margen del navegador solo abriría un escalón entre filas. */
.form-grid > p.field-help { margin: 0; }
/* Tres cifras bastan: el máximo por orden nunca pasa de cien. Reservaba entre 170 y 280 px y se
   comía la fila del boleto, hasta partir «44 disponibles» en dos renglones. */
.sale-quantity-control { min-width: 0; padding: 4px; display: grid; grid-template-columns: 40px 54px 40px; align-items: center; background: white; border: 1px solid #d9dde7; border-radius: 10px; }
.sale-quantity-control button { width: 42px; height: 34px; padding: 0; color: var(--navy); background: #f1f4f8; border: 0; border-radius: 8px; font-size: 1.35rem; font-weight: 500; line-height: 1; }
.sale-quantity-control button:hover:not(:disabled) { color: var(--accent-ink); background: var(--accent); }
.sale-quantity-control button:disabled { cursor: not-allowed; opacity: .38; }
.sale-quantity-control output { min-width: 0; color: var(--navy); text-align: center; font-size: .9rem; font-weight: 600; }
.sale-payment-method-field { align-content: start; align-self: start; }
/* Teléfono: clave de país, número y candado en una sola línea.
 *
 * El envoltorio es un `div` y no un `label` porque dentro hay tres controles y el candado es a su
 * vez una etiqueta; anidar etiquetas no es válido y el navegador reparte los clics como quiere. */
.phone-field { display: grid; gap: 7px; align-content: start; min-width: 0; }
/* Bandera, número y candado en una línea.

   Antes la clave ocupaba un renglón entero porque el desplegable mostraba «+52 · México» y con
   menos de 110 px se cortaba. Reducido a la bandera cabe en 62 px, y el número recupera su
   sitio: diez dígitos son 87 px de texto y aquí tiene de sobra. */
.phone-input { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: center; gap: 8px; }
.phone-country { width: 62px; height: 40px; position: relative; display: grid; place-items: center; flex: 0 0 auto; background: #fff; border: 1px solid #d9dde7; border-radius: 10px; }
/* El `select` cubre la caja entero y es invisible: sigue recibiendo el clic y abriendo su lista
   nativa. Lo que se ve es el `span` de debajo. */
.phone-country select { position: absolute; inset: 0; width: 100%; height: 100%; min-width: 0; padding: 0; opacity: 0; cursor: pointer; }
.phone-country-flag { color: var(--navy); font-size: .82rem; font-weight: 600; letter-spacing: .04em; pointer-events: none; }
/* Cuando el sistema sí dibuja banderas, el glifo pide más tamaño y ningún grosor. */
.phone-country.is-emoji .phone-country-flag { font-size: 1.35rem; font-weight: 400; letter-spacing: 0; }
.phone-country:has(select:focus-visible) { outline: 3px solid var(--accent-ring); outline-offset: 2px; }
/* Un campo por renglón en el punto de venta: se captura de arriba abajo sin saltar de una
   columna a otra, y cada uno ocupa el renglón entero. */
.sales-form-panel .form-grid { grid-template-columns: minmax(0, 1fr); }
/* El rótulo y su ayuda, en la misma línea. `align-items: baseline` los sienta sobre el mismo
   renglón pese a medir distinto, y el `wrap` deja que la ayuda baje sola en una pantalla
   estrecha en lugar de recortarse. */
.field-label { display: flex; align-items: baseline; flex-wrap: wrap; gap: 6px; }
/* Ningún tope por campo. Los había —420 px el nombre y el correo, 240 el teléfono, 200 el método de
   pago— y con el panel entero para el evento eran cuatro anchos distintos y parecidos, que se leen
   como un defecto: el ojo busca la regla y no la encuentra. El ancho lo decide ahora la columna, y
   el teléfono se estira entre su bandera y su candado. */
.phone-input input { min-width: 0; }
.phone-lock { width: 40px; height: 40px; display: grid; place-items: center; flex: 0 0 auto; color: var(--muted); background: #fff; border: 1px solid #d9dde7; border-radius: 10px; cursor: pointer; }
.phone-lock input { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }
.phone-lock svg { width: 18px; height: 18px; display: block; }
.phone-lock:has(input:checked) { color: var(--accent-ink); background: var(--accent); border-color: var(--accent); }
.phone-lock:has(input:focus-visible) { outline: 3px solid var(--accent-ring); outline-offset: 2px; }
.sale-payment-method-field select { min-height: 44px; }
.sale-summary { padding: 15px; display: flex; align-items: center; justify-content: space-between; color: white; background: var(--navy); border-radius: 12px; }
.sale-summary strong { font-size: 1.25rem; }
.sales-history-panel { margin-top: 22px; min-width: 0; overflow: hidden; }
.sales-history-panel table { min-width: 1050px; }
.payment-resolution-panel { margin-top: 22px; min-width: 0; overflow: hidden; }
.payment-resolution-panel table { min-width: 1080px; }
.payment-resolution-panel > .settings-help { margin: -4px 0 18px; }
/* Igual que en el padrón: sacar el botón del grupo de controles deja la cabecera con tres hijos, y
   el reparto genérico los separaría a partes iguales. Empujando el grupo, controles y botón viajan
   juntos al extremo derecho, que es donde estaban. */
.order-history-header { align-items: end; justify-content: flex-start; gap: 10px; }
.order-history-header > .order-history-controls { margin-left: auto; }
/* Anchos fijos, sin `min(…, 100%)`, en los dos controles de esta fila.

   La fila se ajusta a su contenido, y para medir ese contenido el navegador trata el 100 %
   como si no existiera: el filtro aportaba el ancho de su texto —149 px— en lugar de sus
   190. La fila salía de 439 px, y ya colocada dentro el filtro reclamaba sus 190 reales,
   sumaban 480 y el buscador caía debajo del filtro. Medido en los dos pasos. */
.order-history-controls { flex: 0 0 auto; display: flex; align-items: end; flex-wrap: wrap; gap: 10px; }
.order-search { width: 280px; }
.order-search input { width: 100%; }
/* La ficha del evento. El póster manda el ancho y lo demás se apila debajo. */
.sale-event-card { display: grid; gap: 14px; min-width: 0; }
.sale-event-poster { width: 100%; max-height: 240px; display: block; object-fit: cover; border-radius: 13px; }
.sale-event-poster-empty { min-height: 120px; display: grid; place-items: center; color: var(--muted); background: #eef2f8; border-radius: 13px; font-size: .7rem; text-align: center; }
.sale-event-card h3 { margin: 0; font-size: 1.05rem; }
.sale-event-meta { display: grid; gap: 8px; margin: 0; }
.sale-event-meta div { display: grid; grid-template-columns: 84px minmax(0, 1fr); gap: 10px; }
.sale-event-meta dt { color: var(--muted); font-size: .68rem; text-transform: uppercase; letter-spacing: .08em; }
.sale-event-meta dd { margin: 0; min-width: 0; font-size: .78rem; overflow-wrap: anywhere; }
.sale-event-description { margin: 0; color: var(--muted); font-size: .75rem; line-height: 1.5; overflow-wrap: anywhere; }
/* Los términos admiten cinco mil caracteres: sin tope estiran la ficha hasta dejar el
   formulario de venta diminuto al lado. Se desplazan dentro de su propia caja. */
.sale-event-terms { max-height: 150px; padding: 11px 13px; overflow-y: auto; background: var(--surface); border: 1px solid var(--line); border-radius: 11px; }
.sale-event-terms p { margin: 0 0 6px; color: var(--muted); font-size: .72rem; line-height: 1.5; overflow-wrap: anywhere; }
.sale-event-terms p:last-child { margin-bottom: 0; }
/* Se estrecha el diálogo, no la tarjeta de dentro.

   Estaba al revés: la tarjeta medía 420 px dentro de un diálogo de 760 —el ancho común de todos—, así
   que la ventana salía con 340 px de blanco a la derecha. En el teléfono no se notaba, porque ahí los
   dos anchos resuelven al ancho de la pantalla. */
#order-share-dialog { width: min(420px, calc(100% - 28px)); }
.order-share-card .receipt-share-options { margin-top: 4px; }
.order-status-filter { width: 190px; }
.order-environment { display: inline-flex; padding: 4px 8px; border-radius: 999px; color: #344054; background: #eef1f5; font-size: .56rem; font-weight: 600; text-transform: uppercase; white-space: nowrap; }
.environment-production { color: #075e46; background: #dff8ee; }
.environment-test { color: #704f00; background: #fff3c4; }
.mobile-scroll-hint { display: none; }
.report-filter-bar { margin-bottom: 18px; display: grid; grid-template-columns: repeat(5, minmax(120px, 1fr)) auto; align-items: end; gap: 12px; }
/* Actualizar, sin palabra. Cuadrado y de 44 px —la medida cómoda para el dedo, la misma que usan
   los botones de icono de Configuración— y sentado sobre la línea de los campos, que se alinean por
   su base. El `padding` del botón general sobra cuando dentro solo hay un icono. */
.filter-bar-refresh { width: 44px; height: 44px; padding: 0; display: grid; place-items: center; align-self: end; }
.filter-bar-refresh svg { width: 22px; height: 22px; }
/* En escritorio el botón es solo el icono; la palabra existe en el marcado y la enseña el teléfono.
   Se esconde con `display: none` y no visualmente porque aquí no aporta nada: su equivalente para
   lectores de pantalla ya vive en `aria-label`, y repetirlo haría que se anunciara dos veces. */
.filter-bar-refresh-label { display: none; }
/* Diez pistas, no cinco.
 *
 * Los dos renglones comparten las columnas de la rejilla, así que estrechar la quinta para «Filas»
 * estrecharía también «Hasta», que está justo encima. Con diez pistas y dos por campo, el primer
 * renglón queda exactamente como estaba —229 px por campo, la misma cuenta que con cinco columnas—
 * y el segundo puede repartirse distinto: «Buscar» pide sitio para un nombre o un correo y se lleva
 * tres, «Filas» muestra dos dígitos y se conforma con una. */
.report-filter-grid { display: grid; grid-template-columns: repeat(10, minmax(0, 1fr)); align-items: end; gap: 13px; }
.report-filter-grid > label { grid-column: span 2; }
.report-filter-grid > .report-search { grid-column: span 3; }
.report-filter-grid > .report-rows { grid-column: span 1; }
/* Un `input` de texto y un `select` no miden igual con el mismo relleno: son dos píxeles que, con
   la rejilla alineada por la base, dejan «Buscar» un escalón por debajo de sus vecinos. */
.report-filter-grid input { min-height: 42px; }
.date-range-error { grid-column: 1 / -1; margin: 0; padding: 10px 12px; border: 1px solid #f4b4b4; border-radius: 10px; color: #b42318; background: #fff1f1; font-size: .7rem; font-weight: 500; }
.date-range-error[hidden] { display: none; }
.report-filter-bar input[aria-invalid="true"], .report-filter-grid input[aria-invalid="true"] { border-color: #d92d20; background: #fff8f7; }
.report-filters { margin-bottom: 18px; }
/* Aplicar filtros y las dos exportaciones, en una fila bajo los filtros. Envuelve sola, así que en
   pantalla angosta lo que no cabe baja en lugar de encogerse. */
.report-actions-row { margin-top: 14px; display: flex; flex-wrap: wrap; align-items: center; gap: 10px; }
/* Exportar, en dos pisos: la flecha dice que algo se descarga y las tres letras, en qué formato.
   Son dos botones que hacen lo mismo con distinto formato, así que el dibujo solo no los
   distinguiría —la diferencia son las letras—. El rótulo completo vive en `aria-label` y `title`,
   que es lo único que lo nombra para un lector de pantalla y al apuntar con el ratón. */
.export-icon { width: 46px; height: 46px; padding: 0; display: grid; place-items: center; align-content: center; gap: 2px; line-height: 1; }
.export-icon svg { width: 17px; height: 17px; }
.export-icon span { font-size: .53rem; font-weight: 700; letter-spacing: .03em; }
.report-summary { margin-top: 0; }
.sales-category-summary { margin-bottom: 18px; }
.sales-category-summary > p { margin: -6px 0 16px; color: var(--muted); font-size: .72rem; }
.sales-category-summary table { min-width: 640px; }
#sales-report-list .table-wrap { max-width: 100%; }
#sales-report-list table { min-width: 1320px; }
.report-pagination { padding-top: 18px; display: flex; align-items: center; justify-content: center; gap: 14px; color: var(--muted); font-size: .72rem; }
.pagination-status-part { display: inline-block; white-space: nowrap; }
.analytics-grid { margin-bottom: 18px; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; }
.analytics-panel { min-width: 0; }
.analytics-panel-wide { grid-column: 1 / -1; }
.chart-host { min-height: 220px; }
.line-chart { width: 100%; height: 190px; overflow: visible; }
.line-chart path { fill: none; stroke: #dce3ec; stroke-width: 1; vector-effect: non-scaling-stroke; }
.line-chart polyline { fill: none; stroke: var(--accent); stroke-width: 3; stroke-linejoin: round; stroke-linecap: round; vector-effect: non-scaling-stroke; }
.chart-axis { display: flex; justify-content: space-between; gap: 12px; color: var(--muted); font-size: .62rem; }
.chart-axis strong { color: var(--navy); }
.bar-chart { display: grid; gap: 12px; }
.bar-chart-row { display: grid; grid-template-columns: minmax(90px, 1fr) minmax(90px, 2fr) auto; align-items: center; gap: 10px; font-size: .66rem; }
.bar-chart-row > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bar-chart-row i { height: 9px; overflow: hidden; display: block; background: #edf1f6; border-radius: 999px; }
.bar-chart-row b { height: 100%; display: block; background: linear-gradient(90deg, var(--accent), #4d9cff); border-radius: inherit; }
.bar-chart-row strong { font-size: .62rem; white-space: nowrap; }
.compact-input { width: min(250px, 100%); }
.checkin-console { display: grid; grid-template-columns: minmax(330px, 5fr) minmax(380px, 7fr); overflow: hidden; background: white; border: 1px solid var(--line); border-radius: 20px; box-shadow: 0 12px 34px rgba(11, 28, 48, .07); }
.scanner-lens { min-height: 430px; position: relative; overflow: hidden; display: grid; place-items: center; isolation: isolate; color: white; background: radial-gradient(circle at center, #20242b, #06080b 72%); }
.qr-reader { position: absolute; inset: 0; z-index: 0; background: #090c10; }
.qr-reader video { width: 100% !important; height: 100% !important; object-fit: cover; }
.qr-reader > div:not([id]) { display: none !important; }
.scanner-grid { position: absolute; inset: 0; z-index: 1; pointer-events: none; opacity: .2; background-image: linear-gradient(rgba(255,255,255,.14) 1px, transparent 1px), linear-gradient(90deg, rgba(255,255,255,.14) 1px, transparent 1px); background-size: 38px 38px; }
/* Mide la pantalla con las barras del navegador a la vista (`svh`), que JavaScript no puede leer
   directamente: `innerHeight` crece al esconderse esas barras. Ver `fitCheckinConsole()`. */
.checkin-viewport-probe { width: 0; height: 100vh; height: 100svh; position: fixed; top: 0; left: 0; visibility: hidden; pointer-events: none; }
.scanner-lens::after { content: ""; position: absolute; inset: 0; z-index: 1; pointer-events: none; background: radial-gradient(circle at center, transparent 25%, rgba(0,0,0,.48) 100%); }
.scanner-corner { width: 54px; height: 54px; position: absolute; z-index: 3; border-color: white; border-style: solid; pointer-events: none; }
.corner-top-left { top: 35px; left: 35px; border-width: 4px 0 0 4px; border-radius: 8px 0 0; }
.corner-top-right { top: 35px; right: 35px; border-width: 4px 4px 0 0; border-radius: 0 8px 0 0; }
.corner-bottom-left { bottom: 35px; left: 35px; border-width: 0 0 4px 4px; border-radius: 0 0 0 8px; }
.corner-bottom-right { right: 35px; bottom: 35px; border-width: 0 4px 4px 0; border-radius: 0 0 8px; }
.scanner-line { display: none; position: absolute; left: 14%; right: 14%; top: 50%; z-index: 3; height: 2px; background: #ff3040; box-shadow: 0 0 16px #ff3040; animation: scan 2.2s ease-in-out infinite; }
.scanner-lens.is-scanning .scanner-line { display: block; }
@keyframes scan { 0%, 100% { transform: translateY(-112px); } 50% { transform: translateY(112px); } }
/* La línea sobre el recuadro de lectura real. Ver `followScannerBox()` en app.js.

   El elemento se coloca encima del cuadro, con su tamaño, y la barra roja es su `::after`, que va de
   arriba abajo con `top` en porcentaje. Así la animación no necesita variables dentro de
   `@keyframes`, que no todos los navegadores de teléfono resuelven. Sin la clase `scan-line-aligned`
   todo queda como antes: la regla de arriba sigue siendo la de siempre. */
.scanner-lens.scan-line-waiting .scanner-line { visibility: hidden; }
.scanner-lens.scan-line-aligned .scanner-line { top: var(--scan-top); right: auto; left: var(--scan-left); width: var(--scan-width); height: var(--scan-height); background: none; box-shadow: none; animation: none; transform: none; pointer-events: none; }
.scanner-lens.scan-line-aligned .scanner-line::after { content: ""; height: 2px; position: absolute; top: 3px; right: 0; left: 0; background: #ff3040; box-shadow: 0 0 16px #ff3040; animation: scan-recuadro 2.2s ease-in-out infinite; }
@keyframes scan-recuadro { 0%, 100% { top: 3px; } 50% { top: calc(100% - 5px); } }
.scanner-live-badge { position: absolute; top: 18px; left: 50%; z-index: 4; transform: translateX(-50%); padding: 7px 12px; color: white; background: rgba(5, 8, 12, .72); border: 1px solid rgba(255,255,255,.24); border-radius: 999px; backdrop-filter: blur(8px); font-size: .6rem; font-weight: 600; letter-spacing: .09em; text-transform: uppercase; }
.scanner-exit { width: 44px; height: 44px; padding: 0; position: absolute; top: max(12px, env(safe-area-inset-top)); right: max(12px, env(safe-area-inset-right)); z-index: 7; display: none; place-items: center; color: white; background: rgba(5,8,12,.72); border: 1px solid rgba(255,255,255,.28); border-radius: 50%; backdrop-filter: blur(8px); font: inherit; font-size: 1.55rem; line-height: 1; cursor: pointer; }
.scanner-state { max-width: 310px; padding: 25px; position: relative; z-index: 4; text-align: center; }
.scanner-state .scanner-symbol { width: 58px; height: 58px; margin: 0 auto 15px; display: grid; place-items: center; color: #d9e0e9; background: rgba(255,255,255,.1); border: 1px solid rgba(255,255,255,.13); border-radius: 50%; font-size: 1.8rem; }
.scanner-state strong { display: block; font-size: 1.05rem; }
.scanner-state p { margin: 7px 0 0; color: #9da9b8; font-size: .7rem; line-height: 1.5; }
.scanner-state.is-processing .scanner-symbol { color: white; background: rgba(239,68,68,.75); animation: pulse 1s infinite; }
.scanner-state.is-success, .scanner-state.is-error { width: min(310px, calc(100% - 32px)); max-width: none; padding: 0; }
.scanner-result-card { padding: 22px; border-radius: 16px; box-shadow: 0 16px 45px rgba(0,0,0,.25); }
.scanner-state.is-success { color: #073b2a; }
.scanner-state.is-success .scanner-result-card { background: rgba(236,253,245,.96); border: 1px solid rgba(167,243,208,.9); }
.scanner-state.is-success .scanner-symbol { color: white; background: var(--green); border-color: var(--green); }
.scanner-state.is-success p { color: #356758; }
.scanner-state.is-error { color: #7f1d1d; }
.scanner-state.is-error .scanner-result-card { background: rgba(254,242,242,.97); border: 1px solid #fecaca; }
.scanner-state.is-error .scanner-symbol { color: white; background: var(--red); border-color: var(--red); }
.scanner-state.is-error p { color: #934545; }
.scanner-result-action { width: 100%; margin-top: 16px; }
.scanner-lens.has-result::before { content: ""; position: absolute; inset: 0; z-index: 3; background: rgba(2,6,12,.64); backdrop-filter: blur(2px); }
@keyframes pulse { 50% { transform: scale(.9); opacity: .7; } }
.checkin-controls { padding: clamp(26px, 4vw, 46px); display: flex; flex-direction: column; justify-content: center; gap: 17px; }
.checkin-controls h2 { margin: 0; max-width: 520px; font-size: clamp(1.5rem, 3vw, 2.15rem); letter-spacing: -.045em; }
.checkin-copy { max-width: 560px; margin: -6px 0 2px; color: var(--muted); font-size: .76rem; line-height: 1.6; }
/* El selector de evento ya no va dentro de un rótulo, y los campos llevan `font: inherit`. Suelto
   heredaba los 16 px y el peso ligero del formulario en vez de la tipografía de las etiquetas:
   crecía cinco píxeles y, con Montserrat, «Selecciona un evento para activar el lector» se cortaba
   en el teléfono. Esta línea le devuelve lo que antes le daba su `<label>`. */
#checkin-event { font-size: .76rem; font-weight: 500; }
.checkin-badges { display: flex; align-items: center; gap: 9px; }
.camera-status { display: inline-flex; align-items: center; gap: 6px; color: #087451; font-size: .62rem; font-weight: 600; text-transform: uppercase; }
.camera-status::before { content: ""; width: 7px; height: 7px; background: var(--green); border-radius: 50%; box-shadow: 0 0 0 4px rgba(16,185,129,.12); }
.camera-status.is-off { color: #737983; }
.camera-status.is-off::before { background: #9ca3af; box-shadow: none; }
.notice-warning { color: #8a5b00; background: #fff9e8; border: 1px solid #f7dfa0; }
.button-wide { width: 100%; }
.manual-checkin { margin-top: 4px; padding-top: 18px; border-top: 1px solid var(--line); }
.manual-checkin-row { margin-top: 9px; display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 9px; }
.secure-caption { margin: 0; color: #8a8f98; text-align: center; font-size: .61rem; }
.attendees-panel { margin-top: 20px; }
/* Plegar un panel que no es de Configuración. Comparte la caja del botón de allá, pero no su flecha
   giratoria: aquí el signo es «−» cuando el contenido se ve y «+» cuando está plegado, que es lo que
   se pidió. Un solo dibujo para los dos estados: el trazo vertical solo aparece plegado. */
/* Relleno del color que cada quien elige en «Mi cuenta», en los dos tamaños.
 *
 * El color vivía solo en la regla del teléfono. Subirlo aquí evita la duplicación y, con ella, que
 * los dos tamaños puedan separarse el día que alguien retoque uno.
 *
 * Sin `:hover` propio, como `.button-primary`: los botones de acento de esta aplicación no cambian
 * al apuntarlos. El aclarado gris que tenía antes servía cuando el fondo era transparente; sobre
 * color se vería como un parpadeo. El foco lo cubre la regla global con `--accent-ring`. */
.panel-collapse-toggle { width: 35px; height: 35px; padding: 0; display: grid; place-items: center; flex: 0 0 auto; color: var(--accent-ink); background: var(--accent); border: 0; border-radius: 11px; cursor: pointer; }
.panel-collapse-toggle svg { width: 22px; height: 22px; }
.panel-collapse-toggle .toggle-plus-stem { display: none; }
.panel.is-collapsed .panel-collapse-toggle .toggle-plus-stem { display: inline; }
.panel.is-collapsed .panel-collapsible { display: none; }
/* El contador de pendientes viaja pegado al botón, no en mitad de la cabecera. Con `space-between`
   y tres hijos —título, contador y botón— el sobrante se reparte en dos huecos iguales y el
   contador queda flotando en el centro. Con `margin-left: auto` el primer hueco se lo queda entero
   y los dos acaban juntos a la derecha, separados por el `gap` de la cabecera.
   Acotado a las cabeceras que llevan botón de plegado: la de «Notificaciones sin conciliar» no lo
   tiene y, con dos hijos, `space-between` ya la dejaba pegada a la derecha. */
.panel-header:has([data-collapse]) > .status { margin-left: auto; }
/* La cabecera del padrón tiene tres hijos: el título, el grupo de filtros y el botón que pliega.
   Con el reparto de la cabecera genérica —`space-between`— los tres se separarían a partes iguales y
   el grupo quedaría flotando en el centro. Empujándolo con `margin-left: auto`, filtros y botón
   viajan juntos al extremo derecho, que es donde estaban cuando el botón vivía dentro del grupo. */
.attendees-toolbar { justify-content: flex-start; gap: 9px; }
.attendees-toolbar > .attendee-filters { margin-left: auto; }
.attendee-filters { display: flex; align-items: center; gap: 9px; }
.attendee-filters select { width: min(240px, 100%); }
.result-card { padding: 14px; border-radius: 12px; font-size: .75rem; }
.sr-only { width: 1px; height: 1px; padding: 0; margin: -1px; position: absolute; overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0; }
.account-heading-actions { display: grid; justify-items: end; gap: 10px; }
.settings-layout { display: grid; grid-template-columns: minmax(0, 8fr) minmax(250px, 4fr); gap: 20px; align-items: start; }
.settings-main { display: grid; gap: 18px; }
/* El formulario envuelve los seis apartados, no solo los cinco que envía. «Usuarios y
   permisos» va dentro porque tiene que aparecer segundo, entre dos que sí se envían, y no
   hay forma de intercalarlo desde fuera. Puede estar ahí porque no aporta ni un campo: lo
   único que dibuja son botones. Si algún día aporta uno, viajará en los envíos de las cinco
   secciones sin dar síntoma, y por eso el contrato lo vigila. */
.settings-form { display: grid; gap: 18px; }
/* Un elemento de rejilla no baja de la anchura mínima de su contenido: sin esto, la tabla de
   usuarios estira su panel en vez de deslizarse dentro de él. Es el mismo defecto que ya
   apareció en la auditoría y en las organizaciones.

   Son tres rejillas anidadas —columna, formulario y cuerpo de sección— y medido basta con que
   una sola de las tres corte la cadena: quitando cualquiera por separado no pasa nada, y
   quitando las tres el panel de usuarios se va a 1263 px dentro de una columna de 793. Están
   las tres porque cada una cubre su propio eslabón, y la del formulario es la única que cubre
   el anidamiento nuevo: si mañana desaparece la del cuerpo de sección, es ésta la que aguanta. */
.settings-main > * { min-width: 0; }
.settings-form > * { min-width: 0; }
.settings-section { display: grid; gap: 16px; }
/* Un elemento de rejilla no baja de la anchura mínima de su contenido salvo que se le indique, y
   eso rompe cualquier tabla que viva dentro de una de estas secciones. En móvil todas las tablas
   llevan `min-width: 620px` para poder deslizarse dentro de `.table-wrap`; sin esta línea, el
   contenedor se planta en esos 620 píxeles, el panel revienta y **la página entera se desplaza en
   lugar de la tabla** —el encabezado se va fuera de la pantalla por la izquierda—.

   «Usuarios y permisos» nunca lo sufrió porque su panel no es una rejilla: su contenedor se
   encoge solo y la tabla se desliza dentro. Aquí hay que permitirlo de forma explícita.

   Sustituye a una regla puntual que tenía el registro de auditoría, donde el mismo defecto se
   trató como un caso particular de esa tabla; era de todas las listas dentro de estos paneles. */
.settings-section > * { min-width: 0; }
.settings-section-header { min-height: 42px; display: flex; align-items: center; gap: 12px; }
.settings-section-header h3, .panel-header h3 { margin: 3px 0 0; font-size: 1rem; }
/* El cuadro del icono lleva el color de acción, no el azul marino de la marca.
   Antes el rótulo de la sección —que sí usa el acento— quedaba junto a un cuadro que no lo
   seguía, así que elegir «verde» dejaba un rótulo verde al lado de un cuadro azul marino.
   No hace falta una regla por acento: los bloques `html[data-acento=...]` ya redefinen
   `--accent` y, cuando hace falta, `--accent-ink`. */
.settings-icon { width: 38px; height: 38px; display: grid; place-items: center; flex: 0 0 auto; color: var(--accent-ink); background: var(--accent); border-radius: 10px; font-size: .9rem; font-weight: 600; }
.settings-icon svg { width: 23px; height: 23px; display: block; }
/* El cuadro apagado y el icono en color de acción, y no al revés.
   El color de acción identifica de qué trata la tarjeta; el guardar es una acción secundaria que
   solo hace falta cuando ya se cambió algo, y en color pleno pesaba más que el propio contenido.
   Conserva la sombra y gana un cambio de fondo al pasar el cursor: en gris claro y sin ninguna
   de las dos cosas, un botón se lee como desactivado. */
/* Los botones de acción de una cabecera viajan juntos y anclados a la derecha.
   El `margin-left: auto` vivía en el botón de guardar, que se oculta al plegar: al desaparecer,
   la flecha perdía el empuje y saltaba a la izquierda —medido, de 1151 px a 790 en escritorio y
   de 344 a 75 en el teléfono—. En el grupo, el ancla sobrevive a que uno de sus botones falte. */
.settings-section-actions { margin-left: auto; display: flex; align-items: center; flex: 0 0 auto; gap: 6px; }
/* El botón de guardar dice si hay algo que guardar.
   En reposo no lleva fondo ni sombra: solo el icono atenuado dentro de un borde punteado, que se
   lee como «aquí no hay nada pendiente». En cuanto la sección cambia pasa a negro sólido con el
   icono en blanco, y reclama el clic.

   El negro es deliberado y no sigue al acento: el acento identifica de qué trata la sección, y
   este estado dice otra cosa —que hay trabajo sin guardar—, así que conviene que no se confundan.

   Sigue siendo pulsable en reposo. Guardar de más no cuesta nada, y si la detección de cambios
   fallara en algún campo, un botón desactivado dejaría sin salida. */
.settings-card-save { width: 44px; height: 44px; display: grid; place-items: center; flex: 0 0 auto; color: var(--navy); background: none; border: 1px dashed #cbd3e1; border-radius: 11px; opacity: .5; transition: opacity .15s, background .15s, color .15s, border-color .15s; }
.settings-card-save:hover { opacity: 1; border-color: var(--navy); }
.settings-card-save svg { width: 22px; height: 22px; }

/* Con cambios sin guardar. La clase la pone la interfaz al comparar la sección con lo que trajo
   el servidor. */
.has-changes .settings-card-save { color: white; background: #050505; border: 1px solid #050505; opacity: 1; box-shadow: 0 6px 14px rgba(5, 5, 5, .22); }
.has-changes .settings-card-save:hover { background: #202020; transform: translateY(-1px); }

/* «Nuevo usuario» comparte la forma pero no el estado: no guarda nada, abre un diálogo. */
.settings-card-action { width: 44px; height: 44px; display: grid; place-items: center; flex: 0 0 auto; color: var(--navy); background: none; border: 1px solid var(--line); border-radius: 11px; }
.settings-card-action:hover { border-color: var(--navy); background: #f1f3f7; }
.settings-card-action svg { width: 22px; height: 22px; }

/* Contraer una sección.
   El botón es más discreto que el de guardar —no tiene fondo hasta que se apunta— porque no
   opera sobre los datos: solo decide qué se ve. */
.settings-card-toggle { width: 44px; height: 44px; display: grid; place-items: center; flex: 0 0 auto; color: var(--muted); background: none; border: 0; border-radius: 11px; }
.settings-card-toggle:hover { color: var(--navy); background: #f1f3f7; }
.settings-card-toggle svg { width: 20px; height: 20px; transition: transform .18s ease; }
.settings-section-body { display: grid; gap: 16px; }
/* El cuerpo es ahora el elemento de rejilla de la sección; lo que lleva dentro necesita el mismo
   permiso para encogerse, o una tabla ancha volvería a estirar el panel. */
.settings-section-body > * { min-width: 0; }

/* Contraída, la sección esconde su cuerpo y también su botón de guardar: guardar algo que no se
   ve invita a dudar de qué se guardó. La flecha gira para decir en qué estado está. */
/* Plegado, el bloque entero invita al clic; y la cabecera lo hace siempre, porque alterna en los
   dos sentidos. Solo se marcan las cabeceras que tienen flecha: las de la vista Plataforma y las
   de «Mi cuenta» no se pliegan y no deben prometer nada.

   `user-select` es lo que quita el destello azul al pulsar: sin él, pulsar y arrastrar un poco
   sobre el título lo selecciona, y un doble clic selecciona la palabra. A cambio, el título de la
   sección deja de poder copiarse; en un encabezado que funciona como botón compensa.

   Y ese mismo `user-select` es lo que sustituye a la comprobación de texto seleccionado que había
   en el manejador: si aquí no se puede seleccionar, arrastrar no crea selección que estorbe.

   `-webkit-tap-highlight-color` quita el otro resalte, el que pinta el navegador del teléfono
   sobre cualquier elemento pulsable. */
.panel.is-collapsed,
.settings-section-header:has([data-collapse]),
.panel-header:has([data-collapse]) {
    cursor: pointer; user-select: none; -webkit-tap-highlight-color: transparent;
}
/* El grupo de acciones no pliega nada, así que no promete el clic de la cabecera. Sus botones sí
   lo prometen, cada uno por su cuenta. */
.settings-section-actions { cursor: default; }
.settings-card-save, .settings-card-toggle, .settings-card-action { cursor: pointer; }
.is-collapsed .settings-section-body { display: none; }
/* Solo el de guardar. «Nuevo usuario» se queda: no opera sobre lo que se esconde —abre un
   diálogo para dar de alta a alguien, y eso tiene sentido con la lista plegada o sin ella—.

   Y se oculta con `visibility`, no con `display`. Con `display: none` el botón deja de ocupar
   sus 44 píxeles, el título recupera ese ancho, reflúye a menos líneas y la cabecera encoge:
   la flecha subía ocho píxeles al plegar. Reservando el hueco, no se mueve nada. El botón sigue
   sin poder pulsarse ni recibir el foco. */
.is-collapsed .settings-card-save { visibility: hidden; }
.is-collapsed .settings-card-toggle svg { transform: rotate(-90deg); }
.organization-profile-grid { row-gap: 18px; }
.organization-profile-grid > label { align-self: start; width: 100%; }
.field-help { color: #98a2b3; font-size: .64rem; font-weight: 300; line-height: 1.45; }
.profile-public-id { padding-top: 2px; }
.banner-field { display: grid; gap: 11px; min-width: 0; }
/* Zona de carga del póster. El punteado es la convención visual de «suelta algo aquí»; aquí
   solo indica dónde se carga, porque el archivo se elige con el botón. */
.banner-dropzone { padding: 26px 20px; display: grid; justify-items: center; gap: 9px; border: 1px dashed #c3ccdb; border-radius: 14px; text-align: center; }
.banner-dropzone-icon { color: var(--accent); }
.banner-dropzone-icon svg { width: 34px; height: 34px; display: block; }
.banner-dropzone strong { font-size: .92rem; font-weight: 500; }
.banner-file-button { cursor: pointer; }
.banner-dropzone .banner-file-button { margin-top: 4px; }
/* El nombre del archivo elegido puede ser larguísimo: se recorta en una línea en lugar de
   estirar el recuadro. */
.banner-file-name { max-width: 100%; overflow: hidden; color: var(--muted); font-size: .68rem; text-overflow: ellipsis; white-space: nowrap; }
.banner-guidance { display: block; max-width: 430px; }
/* Mismo tratamiento en la vista previa del diálogo, y por el mismo motivo: aqui se veia todavia
   menos, un 26 % del cartel vertical. Alto fijo en vez de `min-height` porque el fondo necesita una
   altura contra la que recortarse. */
.banner-preview { height: 280px; position: relative; overflow: hidden; background: #eef2f8; border: 1px solid var(--line); border-radius: 13px; }
.banner-preview[hidden] { display: none; }
.banner-preview-backdrop { width: 100%; height: 100%; position: absolute; inset: 0; z-index: 0; display: block; object-fit: cover; filter: blur(20px) brightness(.8) saturate(.9); transform: scale(1.12); }
.banner-preview-image { width: 100%; height: 100%; position: relative; z-index: 1; display: block; object-fit: contain; }
.banner-preview-actions { position: absolute; z-index: 2; right: 10px; bottom: 10px; display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 8px; }
.banner-preview .button { background: rgba(255,255,255,.94); box-shadow: 0 4px 12px rgba(11,28,48,.12); }
.switch-label { margin-left: auto; display: flex; align-items: center; gap: 8px; }
/* El de SMTP bajó de la cabecera al cuerpo, bajo su texto de ayuda: allí no debe empujarse a la
   derecha ni competir por sitio con los botones de acción. */
.switch-label-inline { margin-left: 0; justify-self: start; }
.switch-label input { width: 34px; height: 18px; accent-color: var(--accent); }
.settings-help { margin: -5px 0 0; color: var(--muted); font-size: .7rem; line-height: 1.5; }
.timezone-settings-grid { align-items: start; }
.timezone-preview-field { min-width: 0; display: grid; gap: 7px; }
.timezone-preview-label { color: #344054; font-size: .76rem; font-weight: 500; }
.timezone-preview { min-height: 42px; padding: 11px 14px; display: grid; align-content: center; color: var(--navy); background: #f5f7fb; border: 1px solid var(--line); border-radius: 11px; }
.session-policy-note { margin: 0; }
.session-warning-dialog { width: min(520px, calc(100% - 28px)); }
.session-warning-card { display: grid; gap: 14px; }
.session-warning-card > header, .session-warning-card > footer { margin: 0; }
.session-warning-card > p { margin: 0; color: var(--navy); font-size: .82rem; line-height: 1.55; }
.session-warning-card #session-warning-countdown { color: var(--accent); font-size: 1.15rem; }
.session-warning-card .session-warning-help { color: var(--muted); font-size: .7rem; }
.timezone-preview strong { font-size: .78rem; }
.settings-secret { padding: 18px; display: grid; gap: 14px; background: #f5f7fb; border: 1px solid var(--line); border-radius: 13px; }
.settings-secret p { margin: 0; color: var(--muted); font-size: .68rem; }
.payment-configuration-groups { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; align-items: start; }
.payment-configuration-group { min-width: 0; padding: 15px; display: grid; gap: 14px; background: white; border: 1px solid var(--line); border-radius: 11px; }
.payment-configuration-group.environment-test-card { border-top: 3px solid #38bdf8; }
.payment-configuration-group.environment-production-card { border-top: 3px solid #ef4444; }
.payment-configuration-group-title { font-size: .72rem; }
.payment-configuration-group > label { min-width: 0; }
.payment-environment-warning { padding: 12px 14px; display: flex; align-items: center; justify-content: space-between; gap: 12px; border: 1px solid var(--line); border-radius: 11px; }
.payment-environment-warning strong { font-size: .7rem; letter-spacing: .08em; }
.payment-environment-warning span { color: var(--muted); font-size: .66rem; line-height: 1.4; text-align: right; }
.payment-environment-warning.environment-test { color: #075985; background: #e0f2fe; border-color: #7dd3fc; }
.payment-environment-warning.environment-production { color: #991b1b; background: #fef2f2; border-color: #fca5a5; }
.payment-environment-warning.environment-unknown { color: #854d0e; background: #fffbeb; border-color: #fde68a; }
.payment-diagnostics { padding-top: 16px; display: grid; gap: 14px; border-top: 1px solid var(--line); }
.payment-diagnostics-heading { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.payment-diagnostics-heading strong { font-size: .78rem; }
.payment-diagnostics-heading p { margin-top: 3px; }
.payment-diagnostics-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
.payment-diagnostic-card { min-width: 0; padding: 14px; display: grid; gap: 11px; background: white; border: 1px solid var(--line); border-radius: 11px; }
.payment-diagnostic-card h4 { margin: 0; font-size: .78rem; }
.payment-diagnostic-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.payment-diagnostic-row > div { min-width: 0; display: grid; gap: 2px; }
.payment-diagnostic-row strong { font-size: .7rem; }
.payment-diagnostic-row small { color: var(--muted); font-size: .58rem; font-weight: 300; }
.payment-diagnostic-badge { padding: 5px 8px; flex: 0 0 auto; color: #475467; background: #eef2f6; border-radius: 999px; font-size: .55rem; font-weight: 600; text-transform: uppercase; }
.payment-diagnostic-badge.is-ok { color: #047857; background: #dcfce7; }
.payment-diagnostic-badge.is-warning { color: #854d0e; background: #fef3c7; }
.payment-diagnostic-badge.is-error { color: #b42318; background: #fee4e2; }
.payment-diagnostic-card .button { width: 100%; }
/* La conexion de cuenta se dibuja arriba de los campos manuales, asi que no lleva la linea
   superior que separa los diagnosticos del formulario. */
.payment-authorization { padding-top: 0; border-top: 0; }
.payment-authorization-note { margin: 0; color: var(--muted); font-size: .62rem; font-weight: 300; }
.payment-authorization-note.is-warning { padding: 8px 10px; color: #854d0e; background: #fffbeb; border: 1px solid #fde68a; border-radius: 8px; font-weight: 500; }
/* Sin tope de ancho: es un bloque más de la vista Plataforma y se veía estrecho junto al
   remitente de correo y al registro de auditoría, que ocupan la columna entera. */
.platform-gateways { display: grid; gap: 18px; }
/* La contrasena inicial se muestra una sola vez: tiene que ser imposible de pasar por alto. */
/* Selector de tamaño de texto. La muestra «Aa» de cada opción se dibuja al tamaño que esa
   opción produce, para que la elección se vea antes de hacerla. Va en `em` sobre un `rem` fijo
   para que no cambie de golpe cuando el tamaño elegido cambia bajo los propios controles. */
.text-scale-options { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
.text-scale-option { display: flex; align-items: center; gap: 11px; padding: 12px 13px; background: white; border: 1px solid var(--line); border-radius: 11px; cursor: pointer; }
.text-scale-option:hover { border-color: #c3ccdb; }
.text-scale-option:has(input:checked) { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.text-scale-option input { width: auto; margin: 0; flex: 0 0 auto; }
.text-scale-sample { width: 34px; flex: 0 0 auto; text-align: center; font-weight: 600; line-height: 1; }
.text-scale-compacto .text-scale-sample { font-size: .9rem; }
.text-scale-normal .text-scale-sample { font-size: 1.05rem; }
.text-scale-grande .text-scale-sample { font-size: 1.25rem; }
.text-scale-mayor .text-scale-sample { font-size: 1.45rem; }
.text-scale-copy { min-width: 0; display: grid; gap: 2px; }
.text-scale-copy strong { font-size: .76rem; }
.text-scale-copy small { color: var(--muted); font-size: .62rem; line-height: 1.35; }
@media (max-width: 650px) { .text-scale-options { grid-template-columns: 1fr; } }

/* Selector de acento. La muestra de color no usa `var(--accent)`: cada opción tiene que
   enseñar su propio color, no el que está activo. */
.accent-help { margin-top: 4px; font-weight: 500; }
.accent-options { display: flex; flex-wrap: wrap; gap: 9px; }
.accent-option { display: flex; align-items: center; gap: 8px; padding: 8px 12px 8px 9px; background: white; border: 1px solid var(--line); border-radius: 999px; cursor: pointer; }
.accent-option:hover { border-color: #c3ccdb; }
.accent-option:has(input:checked) { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.accent-option input { width: auto; margin: 0; flex: 0 0 auto; accent-color: var(--accent); }
.accent-swatch { width: 18px; height: 18px; flex: 0 0 auto; border-radius: 50%; box-shadow: inset 0 0 0 1px rgba(11, 28, 48, .12); }
.accent-azul .accent-swatch { background: #0058be; }
.accent-verde .accent-swatch { background: #0b7a53; }
.accent-violeta .accent-swatch { background: #5b34c7; }
.accent-grafito .accent-swatch { background: #334155; }
.accent-vino .accent-swatch { background: #722f37; }
.accent-cafe .accent-swatch { background: #4a2c1a; }
.accent-rosa .accent-swatch { background: #e4007c; }
.accent-naranja .accent-swatch { background: #ed7014; }
.accent-name { font-size: .74rem; font-weight: 500; font-variant-numeric: tabular-nums; letter-spacing: .02em; }
/* Los ocho en un renglón en escritorio, encogiéndose.

   Ocho columnas iguales y no un `flex` sin salto: con `flex` cada píldora conserva su ancho
   mínimo y la fila se sale del panel; con la rejilla, `minmax(0, 1fr)` las obliga a repartirse
   lo que haya. Y va **después** de las reglas base: una consulta de medios no añade
   especificidad, así que puesta antes la pisaba el `.accent-option` de arriba. */
@media (min-width: 768px) {
    .accent-options { display: grid; grid-template-columns: repeat(8, minmax(0, 1fr)); gap: 5px; }
    .accent-option { min-width: 0; position: relative; gap: 5px; padding: 8px 6px; }
    .accent-swatch { width: 15px; height: 15px; }
    /* Sin el círculo del radio no caben los ocho: son 19 px por píldora, y con ellos el código
       hexadecimal se quedaba en «#0…» por debajo de 1200 px de ventana. El control sigue ahí,
       enfocable y anunciado; lo que marca la elección es el anillo, que ya existía. */
    .accent-option input { width: 1px; height: 1px; position: absolute; opacity: 0; }
    .accent-option:has(input:focus-visible) { outline: 3px solid var(--accent-ring); outline-offset: 2px; }
    .accent-name { min-width: 0; overflow: hidden; font-size: .64rem; text-overflow: ellipsis; white-space: nowrap; }
}

.audit-filters { max-width: 620px; }
#audit-more { margin-top: 12px; justify-self: start; }
.platform-secret { padding: 16px; display: grid; gap: 8px; color: #7b5b00; background: #fffbeb; border: 1px solid #fde68a; border-radius: 12px; }
.platform-secret strong { font-size: .8rem; }
.platform-secret p { margin: 0; font-size: .68rem; font-weight: 300; line-height: 1.5; }
.platform-secret-value { display: flex; gap: 8px; align-items: center; }
.platform-secret-value input { flex: 1 1 auto; min-width: 0; font-family: ui-monospace, SFMono-Regular, Consolas, monospace; font-size: .78rem; letter-spacing: .04em; }
/* Dos acciones por fila. Envuelven en lugar de estrecharse: una etiqueta partida a la mitad
   se lee peor que un botón en la línea siguiente. */
/* Anchos por omision del registro de auditoria. Suman 100 para que la tabla entre sin
   desbordarse; el reparto refleja cuanto texto lleva cada columna en la practica. */
#platform-audit-list col:nth-child(1) { width: 13%; }
#platform-audit-list col:nth-child(2) { width: 19%; }
#platform-audit-list col:nth-child(3) { width: 15%; }
#platform-audit-list col:nth-child(4) { width: 16%; }
#platform-audit-list col:nth-child(5) { width: 26%; }
#platform-audit-list col:nth-child(6) { width: 11%; }

.row-actions { display: flex; flex-wrap: wrap; gap: 6px; }
/* Acciones de fila con icono, del mismo cuño que los botones de icono de Configuración. El nombre de
   la acción va en `aria-label` y `title`: sin texto visible, es lo único que dice qué hace cada uno. */
.row-icon { width: 40px; height: 40px; display: grid; place-items: center; flex: 0 0 auto; padding: 0; color: var(--navy); background: #fff; border: 1px solid var(--line); border-radius: 10px; cursor: pointer; transition: border-color .15s, background .15s; }
.row-icon:hover { border-color: var(--navy); background: #f1f3f7; }
.row-icon:focus-visible { outline: 3px solid rgba(18, 101, 200, .22); outline-offset: 2px; }
.row-icon svg { width: 20px; height: 20px; }
/* El color dice qué va a pasar antes de pulsar: ámbar para pausar a una organización activa, verde
   para reactivar una suspendida. */
.row-icon-warn { color: var(--amber-ink); }
.row-icon-ok { color: var(--green-ink); }
/* Atenuado pero no desactivado: un botón `disabled` no enseña su `title` en Firefox ni en pantallas
   táctiles, y sin texto visible ese título es la única pista de por qué no se puede. Al pulsarlo,
   `app.js` explica el motivo con un aviso. */
.row-icon[aria-disabled="true"] { color: #a3a9b6; border-style: dashed; cursor: not-allowed; }
.row-icon[aria-disabled="true"]:hover { border-color: var(--line); background: #fff; }
.row-icon:disabled { opacity: .5; cursor: wait; }
.password-reset-organization { margin: 0; font-size: .8rem; }
.platform-gateway { gap: 14px; }
.payment-webhook-url { min-width: 0; font-size: .64rem; }
.payment-webhook-url input { min-width: 0; font-size: .6rem; font-family: ui-monospace, SFMono-Regular, Consolas, monospace; }
.smtp-test-row { padding-top: 15px; display: flex; align-items: center; justify-content: space-between; gap: 12px; border-top: 1px solid var(--line); }
.smtp-test-controls { display: flex; align-items: center; gap: 8px; }
.smtp-test-controls input { width: min(260px, 100%); }
.settings-save { justify-self: end; min-width: 210px; }
/* Fila de acciones al pie de una sección. Un botón suelto dentro de `.settings-section` es un
   elemento de rejilla y se estira a todo el ancho: así salían «Copiar URL» y «Guardar
   aplicación», a 738 píxeles cada uno. Aquí `justify-self` de `.settings-save` no aplica —lo
   alinea la fila— pero su ancho mínimo sí, y eso mantiene los dos botones de «Guardar» de la
   vista del mismo tamaño. */
.settings-actions { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 10px; }
.settings-aside { position: static; }
.settings-aside h3 { margin: 8px 0; font-size: 1.1rem; }
.settings-aside > p { color: var(--muted); font-size: .7rem; line-height: 1.5; }
.settings-status-item { padding: 13px 0; display: flex; align-items: center; justify-content: space-between; gap: 10px; border-top: 1px solid var(--line); font-size: .7rem; font-weight: 500; }
/* Sin `align-items: start`: las dos tarjetas del primer renglón miden lo mismo aunque una tenga
   un párrafo de ayuda más largo que la otra. */
.account-layout { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 20px; }
/* Al estirarse, la sección repartiría el sobrante entre cabecera y cuerpo y abriría un hueco
   entre las dos. Con `auto 1fr` el hueco va entero al cuerpo, y allí lo absorbe el botón. */
.account-layout > .panel { grid-template-rows: auto 1fr; }
.account-layout .settings-section-body { display: flex; flex-direction: column; gap: 16px; }
/* `margin-top: auto` en flex empuja el botón al fondo, de modo que los dos guardados quedan a
   la misma altura en lugar de a media tarjeta. */
.account-layout .settings-section-body > .button { margin-top: auto; }
/* Identidad y Seguridad comparten el primer renglón; la visualización ocupa los dos.

   No hace falta colocar nada a mano: en el marcado van en ese orden, así que basta con que la
   última ocupe las dos columnas. Reordenar con la rejilla habría dejado el tabulador yendo por
   un lado y la vista por otro. */
.account-display-section { grid-column: 1 / -1; }

/* Las secciones de la cuenta se pliegan solo en el teléfono.

   Es la hoja de estilo la que lo acota y no el JavaScript mirando el ancho, que se rompe al
   girar el teléfono o al cambiar el tamaño de la ventana. Aquí, en escritorio: la flecha no se
   dibuja y el cuerpo se ve aunque el estado guardado diga «plegado» —lo cual pasa de verdad,
   porque el estado se comparte con Configuración y con la visita anterior desde el móvil—. */
.account-layout [data-collapse] { display: none; }
.account-layout .is-collapsed .settings-section-body { display: flex; }
.account-layout .settings-section-header { cursor: default; }
.users-panel { margin-top: 0; }
/* «Usuarios y permisos» está armado con `panel-header`, la cabecera de las tablas, y no con la de
   Configuración. De ella heredaba 18 px de margen inferior —plegada, un hueco vacío que la dejaba
   18 px más alta que sus vecinas en escritorio— y 16 px entre icono y texto, que corrían el título
   4 px a la derecha. Aquí toma las medidas de `.settings-section-header`: 12 px de separación, sin
   margen plegada, y desplegada los mismos 16 px que la rejilla de `.settings-section` pone entre
   cabecera y contenido. */
.users-panel > .panel-header { gap: 12px; margin-bottom: 0; }
.users-panel:not(.is-collapsed) > .panel-header { margin-bottom: 16px; }
.factory-reset-panel { margin-top: 20px; display: flex; align-items: center; justify-content: space-between; gap: 20px; border-color: #fecaca; background: #fffafa; }
.factory-reset-panel h2 { margin: 5px 0; }
.factory-reset-panel p { margin: 0; max-width: 700px; color: var(--muted); font-size: .7rem; line-height: 1.5; }
.factory-reset-panel .button { flex: 0 0 auto; }
/* Solo el icono, en todos los anchos. Con las palabras, «Editar» y «Desactivar» medían unos 26 px
   más cada uno y a 1280 px la tabla —que vive en la columna de Configuración— desbordaba 50 px y
   dejaba el segundo botón a medias tras el desplazamiento; en el teléfono ya se escondían desde
   hacía tiempo, así que la fila ancha era la excepción y no la regla.

   Cada botón queda cuadrado alrededor de su dibujo. Lo que dice qué hace cada uno es `aria-label`
   —para quien usa lector de pantalla— y `title`, que es el rótulo al pasar el cursor: un icono sin
   nombre visible necesita las dos cosas. `flex-wrap` se queda por si un día vuelve a haber tres. */
.user-actions { display: flex; flex-wrap: wrap; gap: 7px; }
.user-actions .button { width: 40px; min-height: 40px; padding: 0; }
.user-actions .button svg { width: 18px; height: 18px; display: block; }

dialog { width: min(760px, calc(100% - 28px)); max-height: calc(100vh - 28px); padding: 0; overflow: auto; border: 0; border-radius: 20px; box-shadow: 0 24px 80px rgba(0,0,0,.25); }
dialog::backdrop { background: rgba(7, 13, 21, .6); backdrop-filter: blur(3px); }
/* Campos que solo tienen sentido en la pantalla completa. El formulario es uno solo y se muda entre
   el dialogo y la pantalla, asi que la diferencia entre «rapido» y «avanzado» la marca esto: en el
   dialogo no se ven, y en cuanto el formulario aterriza en la pantalla aparecen. */
#event-dialog .advanced-only { display: none; }

/* El campo de direccion con sus sugerencias.
 *
 * La lista va en posicion absoluta sobre el resto del formulario: empujando el contenido hacia abajo,
 * cada tecla movia los campos de sitio y elegir con el raton se volvia una porfia.
 *
 * `position: relative` en el contenedor y no en la etiqueta: la lista tiene que alinearse con el
 * campo, no con el texto de arriba.
 */
.address-field { position: relative; display: grid; gap: 6px; }
.address-field > label { color: var(--muted); font-size: .68rem; font-weight: 600; letter-spacing: .02em; }
.address-suggestions { margin: 0; padding: 5px; position: absolute; top: 100%; right: 0; left: 0; z-index: 5; max-height: 220px; overflow-y: auto; background: var(--paper); border: 1px solid var(--line); border-radius: 12px; box-shadow: 0 14px 34px rgba(11, 28, 48, .14); list-style: none; }
.address-suggestions li { padding: 8px 10px; border-radius: 8px; color: var(--navy); font-size: .74rem; line-height: 1.35; cursor: pointer; }
/* El resaltado responde al raton y al teclado por igual: quien recorre la lista con las flechas tiene
   que ver donde esta, no adivinarlo. */
.address-suggestions li:hover, .address-suggestions li.is-active { background: var(--accent-soft); }
/* La atribucion no es decorativa: sus condiciones exigen citar a OpenStreetMap siempre, y a Geoapify
   mientras se use el plan gratuito. */
.address-attribution { margin-top: 2px; }
.dialog-card { padding: 24px; background: white; }

/* La edicion avanzada del evento.
 *
 * Es el mismo `#event-form` del dialogo, mudado a una pantalla completa. Aqui se le da el aspecto
 * de panel que le corresponde y se apagan los dos botones que solo tienen sentido dentro de un
 * dialogo: el de «Edición avanzada» —ya se esta en ella— y la equis de cerrar, que en la pantalla
 * la sustituye el «Volver a eventos» de arriba.
 *
 * Con el doble de ancho disponible, los campos pasan de dos columnas a tres. Es todo lo que gana
 * hoy la pantalla completa; el resto de opciones llegara despues.
 */
/* El nombre del evento va pegado al boton de volver, no en la otra punta: los dos dicen donde
   estas, y separados a lo ancho de la pantalla se leen como dos cosas distintas. */
.event-editor-toolbar { align-items: center; justify-content: flex-start; gap: 14px; }
.event-editor-toolbar p { margin: 0; color: var(--muted); font-size: .8rem; }
.event-editor-host > .dialog-card { padding: 22px; background: var(--paper); border: 1px solid var(--line); border-radius: 16px; box-shadow: 0 5px 18px rgba(11, 28, 48, .035); }
.event-editor-host [data-advanced-edit], .event-editor-host .dialog-card > header .icon-button { display: none; }
.event-editor-host .dialog-card > footer { justify-content: flex-end; }
.dialog-header-actions { display: flex; align-items: center; gap: 10px; }

@media (min-width: 1100px) {
    .event-editor-host .form-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
    /* Los que ya ocupaban las dos columnas del dialogo ocupan ahora las tres. */
    .event-editor-host .form-grid > .span-2 { grid-column: 1 / -1; }
}
.dialog-card header, .dialog-card footer { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.dialog-card header { margin-bottom: 20px; }
.dialog-card footer { margin-top: 20px; justify-content: flex-end; }
.icon-button { width: 36px; height: 36px; border: 0; border-radius: 9px; background: #f1f3f6; font-size: 1.3rem; }
.order-email-button { width: 42px; height: 42px; border: 1px solid #d7deea; border-radius: 11px; background: #fff; color: #0b1c30; font-size: 1.25rem; cursor: pointer; }
.order-email-button:hover { border-color: #1265c8; color: #1265c8; background: #f2f7ff; }
.order-email-button:focus-visible { outline: 3px solid rgba(18, 101, 200, .22); outline-offset: 2px; }
.order-email-button:disabled { opacity: .5; cursor: wait; }
.order-actions { display: flex; align-items: center; gap: 7px; }
/* Comparte la caja del reenvío para que las dos acciones de una fila se vean iguales. */
.order-share-button { width: 42px; height: 42px; display: grid; place-items: center; flex: 0 0 auto; color: #0b1c30; background: #fff; border: 1px solid #d7deea; border-radius: 11px; cursor: pointer; }
.order-share-button svg { width: 19px; height: 19px; display: block; }
.order-share-button:hover { color: var(--accent); border-color: var(--accent); background: var(--accent-soft); }
.order-share-button:focus-visible { outline: 3px solid var(--accent-ring); outline-offset: 2px; }
.order-action-unavailable { color: #98a2b3; }
.receipt { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
.receipt-ticket { margin: 12px 0; padding: 14px 0; border-top: 1px dashed #9ca3af; border-bottom: 1px dashed #9ca3af; }
/* El recibo va en monoespaciada porque imita un ticket impreso; lo de compartir no forma
   parte del ticket, así que vuelve a la tipografía de la interfaz. */
.receipt-share { margin-top: 16px; display: grid; gap: 10px; font-family: "Montserrat", ui-sans-serif, system-ui, sans-serif; }
.receipt-share-options { display: flex; flex-wrap: wrap; gap: 8px; }
.receipt-share-options .button { flex: 1 1 auto; gap: 8px; }
.receipt-share-options svg { width: 19px; height: 19px; display: block; flex: 0 0 auto; }
.notice { padding: 12px; border-radius: 10px; font-size: .74rem; }
.notice-error { color: #9b1c1c; background: #fff0f0; border: 1px solid #ffcaca; }
.notice-success { color: #075c3f; background: #e7faf3; border: 1px solid #a9ead3; }
.notice-warning { color: #755600; background: #fff8dc; border: 1px solid #f3dc8c; }
.notice p { margin: 6px 0 0; line-height: 1.55; }
/* Aviso de Base de datos: los nombres de migración son largos y sin espacios, así que se parten donde
   haga falta en lugar de ensanchar la columna lateral de Configuración. */
.schema-health-notice { margin-top: 10px; line-height: 1.5; }
.schema-health-notice ul { margin: 6px 0 0; padding-left: 18px; }
.schema-health-notice li + li { margin-top: 3px; }
.schema-health-notice code { font-size: .68rem; overflow-wrap: anywhere; }
.schema-health-notice details { margin-top: 8px; }
.schema-health-notice summary { cursor: pointer; font-weight: 600; }
.sale-confirmation-card { display: grid; gap: 18px; }
.sale-confirmation-card > header, .sale-confirmation-card > footer { margin: 0; }
.sale-confirmation-intro { margin: -8px 0 0; color: var(--muted); font-size: .72rem; line-height: 1.55; }
.sale-confirmation-content { display: grid; gap: 14px; }
.sale-confirmation-section { padding: 18px; border: 1px solid var(--line); border-radius: 14px; }
.sale-confirmation-section h3 { margin: 5px 0; font-size: 1.2rem; }
.sale-confirmation-section > p { margin: 0; color: var(--muted); font-size: .7rem; }
.sale-confirmation-grid { margin: 16px 0 0; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px 20px; }
.sale-confirmation-grid > div { min-width: 0; }
.sale-confirmation-grid .span-2 { grid-column: 1 / -1; }
.sale-confirmation-grid dt { margin-bottom: 4px; color: var(--muted); font-size: .62rem; font-weight: 500; text-transform: uppercase; letter-spacing: .04em; }
.sale-confirmation-grid dd { margin: 0; overflow-wrap: anywhere; font-size: .76rem; font-weight: 500; }
.sale-confirmation-total { padding: 16px 18px; display: grid; gap: 10px; color: white; background: var(--navy); border-radius: 14px; }
.sale-confirmation-total > div { display: flex; align-items: center; justify-content: space-between; gap: 20px; }
.sale-confirmation-total span { font-size: .7rem; }
.sale-confirmation-total strong { font-size: 1rem; }
.maintenance-dialog-card { display: grid; gap: 18px; }
.maintenance-dialog-card > header { margin-bottom: 0; }
.maintenance-summary { display: grid; gap: 14px; }
.maintenance-event-state { display: flex; align-items: center; justify-content: space-between; gap: 14px; }
.maintenance-event-state h3 { margin: 5px 0 0; font-size: 1.25rem; }
.maintenance-stats { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 10px; }
.maintenance-stats > div { min-width: 0; padding: 14px; background: #f5f7fb; border: 1px solid var(--line); border-radius: 12px; }
.maintenance-stats span, .maintenance-stats strong { display: block; }
.maintenance-stats span { color: var(--muted); font-size: .62rem; font-weight: 600; text-transform: uppercase; }
.maintenance-stats strong { margin-top: 7px; font-size: 1.3rem; }
.maintenance-detail-counts { display: flex; flex-wrap: wrap; gap: 8px 16px; color: var(--muted); font-size: .64rem; }
.maintenance-detail-counts strong { color: var(--navy); }
.factory-reset-stats { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.danger-zone { margin-top: 20px; overflow: hidden; border: 1px solid #fecaca; border-radius: 15px; }
.danger-zone-heading { padding: 18px; background: #fff7f7; border-bottom: 1px solid #fecaca; }
.danger-zone-heading h3 { margin: 5px 0; }
.danger-zone-heading p { margin: 0; color: var(--muted); font-size: .7rem; line-height: 1.5; }
.maintenance-action { padding: 17px 18px; display: flex; align-items: center; justify-content: space-between; gap: 18px; border-bottom: 1px solid #fee2e2; }
.maintenance-action:last-child { border-bottom: 0; }
.maintenance-action h3 { margin-bottom: 5px; font-size: .84rem; }
.maintenance-action p { margin: 0; max-width: 500px; color: var(--muted); font-size: .68rem; line-height: 1.5; }
.maintenance-action .button { flex: 0 0 auto; }
.maintenance-action-critical { background: #fffafa; }
.maintenance-confirm { display: grid; gap: 16px; }
.maintenance-confirm footer { margin-top: 2px; }
.maintenance-back { width: max-content; padding: 0; color: var(--accent); background: transparent; border: 0; font-size: .72rem; font-weight: 500; }
.toast { position: fixed; right: 22px; bottom: 22px; z-index: 50; max-width: 360px; padding: 13px 16px; color: white; background: #0b1c30; border-radius: 11px; box-shadow: var(--shadow); opacity: 0; pointer-events: none; transform: translateY(8px); transition: .18s; font-size: .75rem; }
.toast.is-visible { opacity: 1; transform: translateY(0); }

@media (min-width: 768px) {
    html, body { height: 100%; overflow: hidden; }
    .app-shell { height: 100%; min-height: 0; overflow: hidden; }
    .workspace { height: calc(100% - 40px); min-height: 0; grid-template-rows: minmax(0, 1fr); overflow: hidden; }
    .sidebar { min-height: 0; position: sticky; top: 0; align-self: start; height: 100%; overflow-y: auto; overscroll-behavior: contain; scrollbar-gutter: stable; }
    .main-content { height: auto; min-height: 0; overflow-x: hidden; overflow-y: auto; overscroll-behavior: contain; }
    #new-event { display: none; }
    .events-empty-state { display: none; }
    .event-create-card { min-height: 360px; padding: 32px; display: flex; align-items: center; justify-content: center; flex-direction: column; gap: 15px; color: var(--navy); background: rgba(255,255,255,.56); border: 2px dashed #9ba7b7; border-radius: 16px; text-align: center; }
    .event-create-icon { width: 104px; height: 78px; display: grid; place-items: center; color: var(--navy); }
    .event-create-icon svg { width: 100%; height: 100%; display: block; }
    .event-create-card h3 { margin: 2px 0 0; font-size: 1.15rem; }
    .event-create-card p { width: 100%; max-width: 270px; margin: 0; padding-bottom: 20px; color: var(--muted); border-bottom: 1px solid #b7c0cc; font-size: .72rem; line-height: 1.5; }
    .event-create-card .button { min-width: 150px; margin-top: 5px; }
}

@media (max-width: 1000px) {
    .stats-grid { grid-template-columns: repeat(2, 1fr); }
    .event-grid { grid-template-columns: repeat(2, 1fr); }
    .two-columns { grid-template-columns: minmax(0, 1fr); }
    .checkin-console, .settings-layout, .account-layout { grid-template-columns: 1fr; }
    .scanner-lens { min-height: 380px; }
    .report-filter-bar, .report-filter-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    /* Con dos columnas, los repartos de diez pistas sobran: cada campo vuelve a ocupar una celda.
       Sin esto, un campo que pide tres pistas desbordaría la rejilla. */
    .report-filter-grid > label, .report-filter-grid > .report-search, .report-filter-grid > .report-rows { grid-column: auto; }
}
/* Menú plegado, sólo en escritorio.
 *
 * Va dentro de un `min-width` a propósito. Un selector como `html[data-menu] .workspace` tiene
 * más especificidad que el `.workspace` del bloque móvil, así que sin acotarlo por ancho
 * ganaría también en el teléfono y rompería la barra horizontal de allí. El orden en el archivo
 * no bastaría para evitarlo. */
@media (min-width: 768px) {
    html[data-menu="plegado"] .workspace { grid-template-columns: 64px minmax(0, 1fr); }
    html[data-menu="plegado"] .sidebar { padding: 24px 10px; }
    /* Queda el distintivo de la organización, centrado; el nombre y el rol se van. */
    html[data-menu="plegado"] .organization { justify-content: center; padding: 0; }
    html[data-menu="plegado"] .organization div { display: none; }
    html[data-menu="plegado"] .nav-item,
    html[data-menu="plegado"] .logout,
    html[data-menu="plegado"] .sidebar-toggle { justify-content: center; padding-left: 0; padding-right: 0; }
    /* El texto se oculta, no se elimina: los lectores de pantalla siguen anunciando cada
       destino, y el atributo `title` da el nombre al pasar el cursor. */
    html[data-menu="plegado"] .nav-label { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
    html[data-menu="plegado"] .nav-item.is-active { box-shadow: inset 3px 0 var(--accent); }
    html[data-menu="plegado"] .sidebar-toggle .nav-icon { transform: rotate(180deg); }
}

@media (max-width: 767px) {
    /* Dos columnas de dato y valor no caben en un teléfono: los valores largos —un correo, una
       dirección web— quedarían partidos en tres renglones cada uno. */
    .detail-list { grid-template-columns: minmax(0, 1fr); }
    .support-factory { flex-direction: column; align-items: stretch; }
    /* 44 px es el mínimo cómodo para un dedo; en escritorio bastan 40. */
    .row-icon { width: 44px; height: 44px; }
    /* Y aquí sí: la flecha aparece y el cuerpo obedece al estado guardado. Va en este bloque
       y no en el de 1000 px, que es el de tabletas: ahí las columnas se apilan pero la
       pantalla sigue siendo grande y no hace falta plegar nada. */
    .account-layout [data-collapse] { display: grid; }
    .account-layout .is-collapsed .settings-section-body { display: none; }
    .account-layout .settings-section-header { cursor: pointer; }
    /* El plegado no existe aquí: la barra ya es horizontal y el botón sobra. */
    .sidebar-toggle { display: none; }
    html { min-width: 0; }
    .login-card { grid-template-columns: 1fr; }
    .login-page { padding: 14px; align-items: start; }
    .login-card { margin-top: max(10px, env(safe-area-inset-top)); border-radius: 22px; }
    .login-brand { min-height: 190px; padding: 28px 24px; }
    .login-brand h1 { font-size: clamp(2.3rem, 14vw, 3.5rem); }
    .login-form { padding: 28px 24px; }
    /* El encabezado de marca se queda fijo igual que la barra de navegación, y esta se pega
       justo debajo en lugar de al borde de la pantalla. La altura vive en una variable porque
       los dos la necesitan: escrita dos veces, un cambio en una dejaría a la otra tapando al
       encabezado o flotando sobre un hueco.
       Va por encima en `z-index` para que la barra pase por debajo al desplazar, y no al revés. */
    :root { --topbar-height: 34px; }
    .topbar { height: var(--topbar-height); padding: 0 14px; font-size: .65rem; position: sticky; top: 0; z-index: 21; }
    .workspace { grid-template-columns: 1fr; grid-template-rows: auto minmax(0, 1fr); }
    .sidebar { min-height: 62px; padding: 8px max(10px, env(safe-area-inset-right)) 8px max(10px, env(safe-area-inset-left)); position: sticky; top: var(--topbar-height); z-index: 20; display: grid; grid-template-columns: 38px minmax(0, 1fr) 38px; align-items: center; gap: 5px; color: var(--navy); background: rgba(255,255,255,.96); border-bottom: 1px solid var(--line); box-shadow: 0 8px 24px rgba(11,28,48,.08); backdrop-filter: blur(14px); }
    .organization { margin: 0; padding: 0; display: block; }
    .organization-mark { width: 36px; height: 36px; color: white; background: var(--navy); border-radius: 10px; }
    .organization div, .logout { display: none; }
    .sidebar nav { display: flex; align-items: center; justify-content: center; gap: 2px; overflow: visible; }
    .nav-item { width: 42px; height: 42px; padding: 10px; flex: 0 0 auto; justify-content: center; color: #77808d; border-radius: 10px; white-space: nowrap; }
    .nav-item:hover { color: var(--navy); background: #eef1f5; }
    .nav-item.is-active { color: white; background: var(--navy); box-shadow: none; }
    .nav-item[data-view="account"] { display: none; }
    .nav-icon { display: block; }
    .nav-label { display: none; }
    .mobile-account { width: 38px; height: 38px; padding: 0; display: grid; place-items: center; color: var(--accent-ink); background: var(--accent); border: 2px solid white; border-radius: 50%; box-shadow: 0 0 0 1px #d9dde7; font-weight: 600; }
    .mobile-account.is-active { color: white; background: var(--navy); box-shadow: 0 0 0 3px var(--accent-veil); }
    .main-content { padding: 0 14px calc(30px + env(safe-area-inset-bottom)); overflow: visible; }
    .page-header { padding: 18px 0; }
    .page-header h1 { font-size: 1.7rem; }
    .user-chip { display: none; }
    .event-grid, .stats-grid, .form-grid { grid-template-columns: 1fr; }
    .report-filter-bar, .report-filter-grid, .analytics-grid { grid-template-columns: 1fr; }
    /* Aquí la barra pasa a una columna y el botón queda solo al pie, donde un cuadro de 44 px con
       un icono dentro no dice qué hace. Con la palabra al lado se lee, y ocupando el renglón entero
       se alinea con los campos en vez de flotar en una esquina. `grid-auto-flow: column` es lo que
       sienta el icono y el texto en la misma línea: la caja es una rejilla, y sin eso se apilarían. */
    .filter-bar-refresh { width: 100%; height: auto; min-height: 46px; padding: 11px 16px; grid-auto-flow: column; justify-content: center; gap: 9px; }
    .filter-bar-refresh-label { display: inline; }
    .analytics-panel-wide { grid-column: auto; }
    /* En el teléfono, un solo renglón: la flecha de atrás, el texto y la de adelante. La palabra se
       oculta —el botón conserva su nombre en `aria-label`— y el botón queda cuadrado alrededor de la
       flecha. El texto toma el centro y, si no cabe, se parte entre sus trozos. */
    .report-pagination { display: grid; grid-template-columns: 44px minmax(0, 1fr) 44px; align-items: center; gap: 10px; text-align: center; }
    .report-pagination .pagination-button { width: 44px; min-height: 44px; padding: 0; }
    .report-pagination .pagination-label { display: none; }
    .bar-chart-row { grid-template-columns: minmax(80px, 1fr) minmax(70px, 1.5fr) auto; }
    .span-2 { grid-column: auto; }
    .panel { padding: 16px; border-radius: 14px; }
    .stat-card { padding: 17px; }
    .panel-header, .view-toolbar { align-items: stretch; flex-direction: column; }
    .view-toolbar .button, .panel-header .button { width: 100%; }
    .compact-input { width: 100%; }
    .table-wrap { margin-right: -16px; margin-left: -16px; padding: 0 16px; overscroll-behavior-inline: contain; }
    table { min-width: 620px; }
    /* Una tabla de columnas ajustables declara `min-width: 100%` para poder crecer al ensancharla
       con el ratón, y esa regla tiene más especificidad que la de arriba: en el teléfono anulaba
       el ancho mínimo y el registro de auditoría se aplastaba en seis columnas de entre 34 y 81
       píxeles en lugar de deslizarse. Aquí no hay ratón —los tiradores están ocultos en pantallas
       táctiles—, así que manda el ancho mínimo y la tabla se desliza como las demás. */
    .table-resizable table { min-width: 620px; }
    th, td { padding: 12px 9px; }
    .sales-form-panel { width: 100%; min-width: 0; }
    .sales-form-panel label, .sales-form-panel input, .sales-form-panel select { min-width: 0; }
    .sales-form-panel > .button { width: 100%; }
    .sales-form-panel .sale-summary { align-items: flex-start; flex-direction: column; gap: 6px; }
    .sales-form-panel .sale-summary strong { max-width: 100%; overflow-wrap: anywhere; }
    .sales-history-panel .table-wrap { width: 100%; max-width: 100%; margin-right: 0; margin-left: 0; padding: 0 0 6px; overflow-x: auto; -webkit-overflow-scrolling: touch; overscroll-behavior-inline: contain; }
    .sales-history-panel table { min-width: 1050px; }
    .payment-resolution-panel .table-wrap { width: 100%; max-width: 100%; padding: 0 0 6px; overflow-x: auto; -webkit-overflow-scrolling: touch; overscroll-behavior-inline: contain; }
    .payment-resolution-panel table { min-width: 1080px; }
    .order-history-header { align-items: stretch; }
    .order-status-filter { width: 100%; }
    .order-history-controls { width: 100%; flex-direction: column; align-items: stretch; }
    .order-search { width: 100%; }
    .sale-event-meta div { grid-template-columns: minmax(0, 1fr); gap: 2px; }
    .mobile-scroll-hint { margin: -8px 0 10px; display: block; color: var(--muted); font-size: .65rem; text-align: right; }
    /* Sólo si debajo hay una tabla que deslizar. Sin registros la lista pinta un «No hay…», mientras
       carga pinta el esqueleto, y si la carga falla queda vacía: en ninguno de los tres hay nada que
       deslizar y el aviso sólo estorba. Se pregunta por la tabla y no por el «vacío» porque esos
       estados no se marcan igual. */
    .mobile-scroll-hint:not(:has(+ * table)) { display: none; }
    .receipt, .receipt-ticket { min-width: 0; overflow-wrap: anywhere; word-break: break-word; }
    .event-cover { height: 170px; }
    .event-actions { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .event-actions .button { min-width: 0; }
    /* El escáner cede su altura para que, bajo las barras fijas, se vean completos el selector,
       «Activar cámara», el folio y «Validar código». Lo que esos controles ocupan lo mide
       `fitCheckinConsole()` y lo deja en `--checkin-reservado`; aquí se resta de la pantalla. Se usa
       `svh` —la pantalla con las barras del navegador a la vista— para que la altura no salte al
       esconderse esas barras mientras se desplaza. Sin la variable queda en los 330 px de siempre.

       Con un resultado a la vista el bloque recupera su altura: la tarjeta de «Boleto aprobado» mide
       hasta 278 px y en un escáner encogido quedaría cortada. Nunca en pantalla completa, que tiene
       la suya. */
    .scanner-lens { min-height: 0; height: clamp(100px, calc(100svh - var(--checkin-reservado, 0px)), 330px); }
    .scanner-lens.has-result:not(.is-mobile-fullscreen) { height: auto; min-height: 330px; }
    /* Pantallas chicas: donde ni encogiendo el escáner quedaría espacio útil. Sin la fila de
       «Lector QR», con menos aire entre los controles, y sin el círculo del estado en reposo, que
       en un escáner bajo no cabe junto a su texto. */
    .checkin-console.is-compact .checkin-badges { display: none; }
    .checkin-console.is-compact .checkin-controls { padding-top: 16px; padding-bottom: 16px; gap: 12px; }
    .checkin-console.is-compact .manual-checkin { padding-top: 12px; }
    /* Ni el círculo ni las esquinas cambian con la cámara abierta: a pantalla completa hay sitio. */
    .checkin-console.is-compact .scanner-lens:not(.is-mobile-fullscreen) .scanner-state:not(.is-success):not(.is-error) .scanner-symbol { display: none; }
    /* Las esquinas miden 54 px a 35 px del borde, pensadas para 330 px de alto. En un escáner de
       cien, la de arriba y la de abajo se montaban una sobre otra y sobre «Escáner listo». */
    .checkin-console.is-compact .scanner-lens:not(.is-mobile-fullscreen) .scanner-corner { width: 26px; height: 26px; }
    .checkin-console.is-compact .scanner-lens:not(.is-mobile-fullscreen) .corner-top-left { top: 12px; left: 12px; }
    .checkin-console.is-compact .scanner-lens:not(.is-mobile-fullscreen) .corner-top-right { top: 12px; right: 12px; }
    .checkin-console.is-compact .scanner-lens:not(.is-mobile-fullscreen) .corner-bottom-left { bottom: 12px; left: 12px; }
    .checkin-console.is-compact .scanner-lens:not(.is-mobile-fullscreen) .corner-bottom-right { right: 12px; bottom: 12px; }
    /* Con el margen real de la página el selector mide unos 304 px en un teléfono de 360, y la flecha
       nativa ocupa parte: «Selecciona un evento para activar el lector» se cortaba en «lecto». */
    #checkin-event { font-size: .7rem; }
    body.scanner-mobile-active { overflow: hidden; overscroll-behavior: none; }
    body.scanner-mobile-active .view.is-active { animation: none; transform: none; }
    .scanner-lens.is-mobile-fullscreen { width: 100vw; width: 100dvw; height: 100vh; height: 100dvh; min-height: 0; position: fixed; inset: 0; z-index: 1000; border-radius: 0; }
    .scanner-lens.is-mobile-fullscreen .scanner-exit { display: grid; }
    .scanner-lens.is-mobile-fullscreen .scanner-live-badge { top: max(18px, calc(env(safe-area-inset-top) + 6px)); }
    .scanner-lens.is-mobile-fullscreen .scanner-state.is-success,
    .scanner-lens.is-mobile-fullscreen .scanner-state.is-error { width: auto; position: absolute; inset: max(70px, calc(env(safe-area-inset-top) + 58px)) max(14px, env(safe-area-inset-right)) max(14px, env(safe-area-inset-bottom)) max(14px, env(safe-area-inset-left)); z-index: 6; display: flex; flex-direction: column; justify-content: space-between; pointer-events: none; }
    .scanner-lens.is-mobile-fullscreen .scanner-result-card { width: 100%; }
    .scanner-lens.is-mobile-fullscreen .scanner-result-action { width: min(280px, calc(100vw - 40px)); min-height: 52px; margin: 0; position: fixed; top: 50%; left: 50%; pointer-events: auto; transform: translate(-50%, -50%); }
    .scanner-lens.is-mobile-fullscreen .scanner-result-action:hover { transform: translate(-50%, calc(-50% - 1px)); }
    .scanner-lens.is-mobile-fullscreen.has-result .scanner-corner,
    .scanner-lens.is-mobile-fullscreen.has-result .scanner-line { opacity: 0; }
    .checkin-controls { padding: 24px; }
    .manual-checkin-row { grid-template-columns: 1fr; }
    .manual-checkin-row .button { width: 100%; }
    .attendee-filters, .smtp-test-row { width: 100%; align-items: stretch; flex-direction: column; }
    .attendee-filters select, .smtp-test-controls input { width: 100%; }
    /* En el teléfono la cabecera se reparte en dos filas: arriba el título y el botón, a la derecha;
       debajo los dos campos, a todo lo ancho. Es una rejilla y no una columna porque solo así el
       botón puede sentarse al lado del título en lugar de apilarse el último. */
    .attendees-toolbar { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: 12px; }
    .attendees-toolbar > div:first-child { grid-column: 1; grid-row: 1; }
    .attendees-toolbar > .panel-collapse-toggle { grid-column: 2; grid-row: 1; }
    .attendees-toolbar > .attendee-filters { grid-column: 1 / -1; margin-left: 0; }
    /* Los tres paneles de venta, con el mismo reparto que el padrón: título y botón arriba —el botón
       a la derecha— y debajo lo que acompañe a cada uno. En los de resolución el contador se queda
       en la primera fila, junto al botón: plegado sigue diciendo si hay algo que resolver, y ahí se
       lee de un vistazo sin ocupar un renglón propio. */
    .order-history-header, .payment-resolution-panel > .panel-header {
        display: grid; grid-template-columns: minmax(0, 1fr) auto auto; align-items: center; gap: 10px 12px;
    }
    .order-history-header > div:first-child, .payment-resolution-panel > .panel-header > div:first-child { grid-column: 1; grid-row: 1; }
    .order-history-header > .panel-collapse-toggle, .payment-resolution-panel > .panel-header > .panel-collapse-toggle { grid-column: 3; grid-row: 1; }
    .payment-resolution-panel > .panel-header > .status { grid-column: 2; grid-row: 1; justify-self: end; }
    .order-history-header > .order-history-controls { grid-column: 1 / -1; grid-row: 2; margin-left: 0; }
    .smtp-test-controls { align-items: stretch; flex-direction: column; }
    /* Sin `flex-wrap`. Lo llevaba para que un título largo respirara, pero lo que hacía era
       tirar los botones a una segunda línea según lo que midiera el texto: con título largo la
       flecha caía 118 px más abajo y con título corto se quedaba arriba. Ahora la cabecera es
       una sola fila —icono, texto flexible, botones— y el texto se ajusta dentro de su caja. */
    .settings-section-header { align-items: center; }
    .settings-section-header > div { min-width: 0; }
    /* «Usuarios y permisos» se iguala a las demás: su cabecera se apilaba en vertical con los
       botones a ancho completo, así que la flecha salía como una barra en lugar de un cuadro. */
    .users-panel .panel-header { align-items: center; flex-direction: row; }
    .users-panel .panel-header > div { min-width: 0; }
    .users-panel .panel-header .button { width: auto; }
    .payment-configuration-groups { grid-template-columns: 1fr; }
    .payment-diagnostics-grid { grid-template-columns: 1fr; }
    .payment-diagnostics-heading { align-items: stretch; flex-direction: column; }
    .payment-environment-warning { align-items: flex-start; flex-direction: column; }
    .payment-environment-warning span { text-align: left; }
    /* La tercera columna medía 44 px, el ancho de un botón, porque solo estaba el de guardar.
       Con el grupo de acciones necesita el suyo: fijada en 44, la flecha se salía del panel
       —medido, su borde derecho quedaba en 394 dentro de un panel que acaba en 344—. */
    .organization-profile-header { display: grid; grid-template-columns: 38px minmax(0, 1fr) auto; align-items: center; }
    /* El sangrado de 50 px alineaba el interruptor bajo el texto cuando vivía en la cabecera,
       pasado el icono. El de SMTP ya está en el cuerpo, donde el panel pone su propio margen:
       sumarle ese sangrado y un ancho del 100 % lo sacaba 19 px fuera de la pantalla. */
    .switch-label { width: 100%; margin-left: 50px; }
    .switch-label-inline { width: auto; margin-left: 0; }
    .settings-save { width: 100%; justify-self: stretch; }
    .banner-dropzone { padding: 22px 14px; }
    .banner-preview { height: 210px; }
    .banner-dropzone .banner-file-button { width: 100%; }
    .account-heading-actions { margin-bottom: 16px; justify-items: stretch; }
    .mobile-logout { width: 100%; display: inline-flex; }
    .maintenance-stats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .maintenance-action { align-items: stretch; flex-direction: column; }
    .maintenance-action .button { width: 100%; }
    .factory-reset-panel { align-items: stretch; flex-direction: column; }
    .factory-reset-panel .button { width: 100%; }
    .factory-reset-stats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .sale-confirmation-grid { grid-template-columns: 1fr; }
    .sale-confirmation-grid .span-2 { grid-column: auto; }
    .sale-confirmation-total > div { align-items: flex-start; flex-direction: column; gap: 3px; }
    /* El máximo por orden y las dos casillas, en el teléfono. Envueltos en una fila, el texto
       quedaba encima de su campo, una casilla al lado y la otra suelta en el renglón de abajo.
       Aquí van en columna: el texto y su selector en un renglón, y cada casilla en el suyo. */
    .event-options-row { flex-direction: column; align-items: flex-start; gap: 8px; }
    .event-options-row > .checkbox-label { align-self: flex-start; min-height: 34px; }
    .order-max-field { display: flex; align-items: center; gap: 20px; }
    /* El mismo aspecto que el selector de cantidad de la venta (`.sale-quantity-control`): caja
       blanca con borde y dos botones grises. El número sigue siendo el campo real y se puede
       escribir; sólo pierde su borde y sus flechas, que aquí harían de tercer par de botones. */
    .order-max-stepper { padding: 4px; display: grid; grid-template-columns: 44px 52px 44px; align-items: center; background: white; border: 1px solid #d9dde7; border-radius: 10px; }
    .order-max-stepper:has(input:focus) { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
    .order-max-stepper button { width: 44px; height: 38px; padding: 0; display: block; color: var(--navy); background: #f1f4f8; border: 0; border-radius: 8px; font-size: 1.35rem; font-weight: 500; line-height: 1; }
    .order-max-stepper button:disabled { cursor: not-allowed; opacity: .38; }
    #event-form .order-max-stepper input[name="max_tickets_per_order"] { width: 100%; height: 38px; padding: 0; color: var(--navy); text-align: center; font-size: .9rem; font-weight: 600; background: transparent; border: 0; box-shadow: none; appearance: textfield; -moz-appearance: textfield; }
    .order-max-stepper input::-webkit-inner-spin-button,
    .order-max-stepper input::-webkit-outer-spin-button { margin: 0; -webkit-appearance: none; }
    dialog { width: calc(100% - 16px); max-height: calc(100dvh - 16px); border-radius: 16px; }
    .dialog-card { padding: 20px 16px; }
    .dialog-card footer { align-items: stretch; flex-direction: column-reverse; }
    .dialog-card footer .button { width: 100%; }
    .toast { right: 12px; bottom: calc(12px + env(safe-area-inset-bottom)); left: 12px; max-width: none; }
}

@media (max-width: 360px) {
    .sidebar { grid-template-columns: 34px minmax(0, 1fr) 34px; padding-right: 7px; padding-left: 7px; }
    .organization-mark, .mobile-account { width: 34px; height: 34px; }
    .sidebar nav { gap: 0; }
    .nav-item { width: 39px; height: 39px; padding: 9px; }
    .main-content { padding-right: 11px; padding-left: 11px; }
    .checkin-controls { padding: 20px 16px; }
}

@media print {
    body * { visibility: hidden; }
    #receipt-dialog, #receipt-dialog * { visibility: visible; }
    #receipt-dialog { position: absolute; inset: 0; width: 80mm; max-height: none; box-shadow: none; }
    #receipt-dialog header button, #receipt-dialog footer { display: none; }
}
/* Categorías de boletos */
.ticket-types-editor{border:1px solid var(--border,#dbe2ec);border-radius:18px;padding:18px;background:#f7f9fc}
.ticket-types-editor summary{display:flex;align-items:center;justify-content:space-between;cursor:pointer;font-weight: 500}
.ticket-types-editor summary small{color:#6f7b8c;font-weight: 300}
#ticket-types-list{display:grid;gap:14px;margin:18px 0}
.ticket-type-editor-row{display:grid;grid-template-columns:1.5fr 1fr 1fr auto;gap:12px;align-items:end;padding:14px;background:#fff;border:1px solid #dbe2ec;border-radius:14px}
.ticket-type-editor-row .icon-button{margin-bottom:2px}
.ticket-type-remove-label{display:none}
/* La lista no crece con el número de categorías: se desplaza dentro de su caja.

   Con diez categorías el total y el botón de completar venta se iban fuera de la pantalla y
   había que bajar para cobrar. El tope es más alto en escritorio que en el teléfono.

   `overflow-y: auto` convierte la caja en contenedor de desplazamiento, y eso hace que el
   horizontal deje de ser `visible`: sin `min-width: 0` en las filas aparecería también una
   barra horizontal. Por eso está la regla de abajo. */
/* Los nombres de quienes entran. Son tantos como boletos lleve la venta, así que la lista se
   desplaza dentro de su propio recuadro en vez de empujar el botón de cobrar fuera de la pantalla. */
.sale-attendee-names{display:grid;gap:9px;margin:0 0 14px;padding:13px;max-height:240px;overflow-y:auto;border:1px dashed #cfd8e6;border-radius:14px}
.sale-attendee-names .eyebrow{margin:0}
.sale-ticket-types{display:grid;gap:10px;margin:14px 0;max-height:320px;overflow-y:auto}
.sale-ticket-types>*{min-width:0}
.sale-ticket-type-row{display:grid;grid-template-columns:minmax(0,1fr) auto auto;align-items:center;gap:16px;padding:14px;border:1px solid #dbe2ec;border-radius:14px}
.sale-ticket-type-name{min-width:0}
.sale-ticket-type-row strong,.sale-ticket-type-row small{display:block}
.sale-ticket-type-name small{margin-top:4px}
/* El precio manda en la fila: es lo que se dice en voz alta al cobrar. */
.sale-ticket-price{font-size:1.5rem;font-weight:500;letter-spacing:-.02em;white-space:nowrap}
/* Cuánto queda de la zona, en color: holgado, a la mitad, apurado y agotado. */
.sale-availability{font-weight:500}
.sale-availability.is-high{color:var(--green-ink)}
.sale-availability.is-medium{color:var(--amber-ink)}
.sale-availability.is-low{color:var(--orange-ink)}
.sale-availability.is-sold-out{color:var(--red-ink)}
@media(max-width:760px){
  .ticket-type-editor-row{grid-template-columns:1fr 1fr}
  .ticket-type-editor-row label:first-child{grid-column:1/-1}
  .ticket-type-editor-row .ticket-type-remove{width:auto;height:auto;min-height:0;margin:2px 0 0;padding:7px 10px;grid-column:1/-1;justify-self:start;display:inline-flex;align-items:center;justify-content:center;gap:6px;color:#fff;background:#c81e1e;border:1px solid #c81e1e;border-radius:8px;font-size:.7rem;font-weight: 500;line-height:1.15}
  .ticket-type-editor-row .ticket-type-remove:hover{background:#a91515;border-color:#a91515}
  .ticket-type-editor-row .ticket-type-remove:disabled{color:#fff;background:#e8a3a3;border-color:#e8a3a3;opacity:.65;cursor:not-allowed}
  .ticket-type-remove-label{display:inline}
  /* Los tres en una línea también aquí: partirlos hacía la tarjeta el doble de alta y la lista
     ya no puede crecer. Lo que se cede es tamaño —precio, contador y relleno— porque el
     nombre y la disponibilidad son lo que hay que leer de un vistazo. */
  .sale-ticket-type-row{gap:8px;padding:12px 10px}
  .sale-ticket-price{font-size:1rem}
  .sale-quantity-control{grid-template-columns:34px 40px 34px;padding:3px}
  .sale-quantity-control button{width:34px;height:30px;font-size:1.15rem}
  .sale-quantity-control output{font-size:.82rem}
  .sale-ticket-types{max-height:250px}
}

/* Los ajustes del contrato comparten diálogo y necesitan una pausa visual entre operaciones. */
#organization-detail-dialog .dialog-card > form + form { border-top: 1px solid var(--line); margin-top: 22px; padding-top: 22px; }
#organization-detail-dialog .dialog-card > form h3 { margin: 0; font-size: .95rem; }
#platform-audit-list details summary { cursor: pointer; white-space: normal; }
.audit-plan-details { display: grid; gap: 5px; padding: 8px 0; white-space: normal; font-size: .75rem; }
