/* Muuch — dashboard del comercio */
:root {
  /* Dos rosas, a propósito (sept. 2026). El magenta de marca da 3.04:1 con
     blanco encima o debajo — por debajo del 4.5:1 que pide WCAG AA para
     texto, y era la causa de 955 de los 996 fallos de contraste medidos en
     toda la plataforma. En vez de cambiar la cara del producto, se parte el
     token en dos:
       --brand      superficies grandes SIN texto encima: fondos, barras,
                    bordes, anillos de foco, decoración.
       --brand-deep el mismo rosa un punto más profundo (4.86:1 con blanco):
                    relleno de botón con texto blanco, texto en rosa, enlaces
                    e íconos. A ojo es el mismo rosa.
     Si agregas un botón o un texto en rosa, va con --brand-deep. */
  --brand: #ff558a;       /* Magenta Acento: fondos, barras, bordes, decoración */
  --brand-deep: #d6246a;  /* El mismo rosa con contraste AA: texto y botones */
  --brand-ink: #fff;      /* La tinta que va ENCIMA del rosa */
  --navy: #0e2533;        /* Marino Primario: header, títulos, footer */
  --bg: #f8f9fa;
  --surface: #fff;
  --border: #e4e7ec;
  --text: #333333;
  --muted: #667085;
  --ok: #067647;
  --ok-bg: #ecfdf3;
  --warn: #b54708;
  --warn-bg: #fffaeb;
  --danger: #b42318;
  --danger-bg: #fef3f2;
  --radius: 12px;
  --shadow: 0 1px 2px rgba(16, 24, 40, .06), 0 1px 3px rgba(16, 24, 40, .1);
  --font-title: 'Montserrat', -apple-system, BlinkMacSystemFont, sans-serif;
}

* { box-sizing: border-box; }

/* El atributo [hidden] debe ganarle a cualquier display: grid/flex de abajo,
   si no, las vistas ocultas se siguen viendo. */
[hidden] { display: none !important; }

/* Sin esto, Safari/WebKit móvil (incluido el navegador embebido de WhatsApp)
   "infla" el tamaño de fuente en columnas angostas para mejorar la
   legibilidad — es justo lo que hacía que "Principiante" (.stat .n, 26px)
   se saliera de su tarjeta en el panel de ventas: en desktop o con Chrome
   de escritorio nunca se ve, sólo en el motor que realmente aplica esa
   heurística. */
html { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; }

body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
  background: var(--bg);
  color: var(--text);
  -webkit-font-smoothing: antialiased;
}

/* textarea va aquí y no sólo en la regla de abajo: sin `font: inherit` el
   navegador le pone su monoespaciada por defecto, y las notas internas del
   admin y del comercio salían en un tipo que no es de ningún lado. */
button, input, select, textarea { font: inherit; }
a { color: var(--brand-deep); }

h1, h2, h3, .card h2 {
  font-family: var(--font-title);
  font-weight: 800;
  color: var(--navy);
  letter-spacing: -.01em;
}
.card .hint, .login-card p.sub { font-weight: 300; }

/* ---------- Login ---------- */
.login-wrap {
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: 20px;
}
.login-card {
  width: 100%;
  max-width: 380px;
  background: var(--surface);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 32px 28px;
}
.login-card h1 { margin: 0 0 4px; font-size: 24px; }
.login-card p.sub { margin: 0 0 24px; color: var(--muted); font-size: 14px; }

/* ---------- Layout ---------- */
/* El único lugar donde domina el marino: la franja utilitaria de arriba
   (marca Muuch + identidad del comercio). Las pestañas, justo debajo,
   vuelven a fondo claro — es donde el comercio pasa el día trabajando. */
.topbar {
  position: sticky;
  top: 0;
  z-index: 20;
}
.topbar-inner {
  background: var(--navy);
  padding: 10px 20px;
  display: flex;
  align-items: center;
  gap: 14px;
}
.topbar-inner-content {
  max-width: 1080px;
  margin: 0 auto;
  width: 100%;
  display: flex;
  align-items: center;
  gap: 14px;
}
.muuch-mark {
  display: flex; align-items: center; gap: 8px;
  flex: none;
  padding-right: 14px;
  border-right: 1px solid rgba(255, 255, 255, .18);
}
.muuch-mark .isotipo {
  width: 26px; height: 26px; border-radius: 50%;
  background: var(--brand-deep);
  color: #fff; display: grid; place-items: center;
  font-family: var(--font-title); font-weight: 800; font-size: 13px;
  flex: none;
}
.muuch-mark .wordmark {
  font-family: var(--font-title); font-weight: 800; font-size: 16px;
  color: #fff; letter-spacing: -.01em;
}
.logo {
  width: 34px; height: 34px;
  border-radius: 9px;
  background: var(--brand-deep);
  color: var(--brand-ink);
  display: grid; place-items: center;
  font-weight: 700; font-size: 15px;
  object-fit: cover;
  flex: none;
}
/* En el header, el logo del comercio (foto propia o iniciales) usa SU
   color — no el magenta de Muuch — para que se sienta como su propia
   marca dentro del panel. El anillo lo separa del fondo marino cuando su
   logo es claro o blanco. */
.topbar-inner .logo { background: var(--biz-brand, var(--brand)); box-shadow: 0 0 0 1px rgba(255, 255, 255, .18); }
.biz-id { min-width: 0; }
.biz-name { font-weight: 650; font-size: 15px; color: #fff; }
.biz-plan { font-size: 12px; color: rgba(255, 255, 255, .65); }
.spacer { flex: 1; }

/* ---------- El encabezado en un teléfono ----------
   Cabían siete cosas en 360px —isotipo, "Muuch", separador, logo del
   comercio, su nombre, su plan, la pastilla de conexión y "Salir"— y el
   resultado era que el nombre del comercio se partía en dos renglones, el
   plan en otros dos, y con "• reconectando" en pantalla el botón de "Salir"
   se salía de cuadro. Aquí se ceden las dos cosas que el comercio no
   necesita leer (nuestra palabra "Muuch" —el isotipo se queda— y el texto de
   la pastilla) para que lo suyo quepa en un renglón. */
@media (max-width: 500px) {
  .topbar-inner { padding: 10px 12px; gap: 10px; }
  .topbar-inner-content { gap: 10px; }
  .muuch-mark { padding-right: 10px; gap: 0; }
  .muuch-mark .wordmark { display: none; }
  .biz-name, .biz-plan { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  /* La pastilla se queda como un punto de color: dice lo mismo (verde = en
     vivo, gris = reconectando) sin empujar a "Salir" fuera de la pantalla. */
  #live-dot { font-size: 0; padding: 0; width: 10px; height: 10px; flex: none; }
  #live-dot.pill-ok { background: var(--ok); }
  #live-dot.pill-muted { background: rgba(255, 255, 255, .45); }
}
.topbar-inner .btn-ghost {
  background: transparent; color: #fff; border-color: rgba(255, 255, 255, .3);
}
.topbar-inner .btn-ghost:hover:not(:disabled) { border-color: var(--brand); color: var(--brand-deep); }
.topbar-inner .pill-muted { background: rgba(255, 255, 255, .12); color: rgba(255, 255, 255, .75); }

.tabs {
  background: var(--surface);
  border-bottom: 1px solid var(--border);
  max-width: 1080px;
  margin: 0 auto;
  padding: 0 20px;
  display: flex;
  gap: 4px;
  overflow-x: auto;
  /* En un teléfono sólo caben ~3 de las 7 pestañas y nada avisaba que
     hubiera más: "Mi Negocio", "Plan" y "Puntos Muuch" quedaban invisibles
     para quien nunca arrastra una barra que no parece arrastrable. El
     degradado del borde derecho es esa señal, y se apaga solo al llegar al
     final (scroll-timeline no está en todos lados, así que se resuelve con
     un fondo doble anclado al viewport del scroller: el clásico de Lea
     Verou, sin JS). */
  background:
    linear-gradient(to right, var(--surface) 30%, rgba(255, 255, 255, 0)) left center / 24px 100% no-repeat local,
    linear-gradient(to left, var(--surface) 30%, rgba(255, 255, 255, 0)) right center / 24px 100% no-repeat local,
    radial-gradient(farthest-side at 0 50%, rgba(15, 23, 42, .14), rgba(15, 23, 42, 0)) left center / 14px 100% no-repeat scroll,
    radial-gradient(farthest-side at 100% 50%, rgba(15, 23, 42, .14), rgba(15, 23, 42, 0)) right center / 14px 100% no-repeat scroll,
    var(--surface);
  scrollbar-width: none;
}
.tabs::-webkit-scrollbar { display: none; }
.tab {
  border: 0; background: none;
  padding: 12px 14px;
  color: var(--navy);
  font-weight: 550; font-size: 14px;
  cursor: pointer;
  border-bottom: 2px solid transparent;
  white-space: nowrap;
}
.tab[aria-selected="true"] { color: var(--navy); font-weight: 650; border-bottom-color: var(--brand); }
.tab:hover:not([aria-selected="true"]) { color: var(--brand-deep); }
/* Cinco pestañas tienen que caber de un vistazo en un teléfono chico: sin
   esto, la quinta se sale de cuadro y vuelve el problema que tenían las 7.
   Encogerlas a un tamaño fijo no alcanzaba: a 420px sí caben, pero a 360px
   (Android de pantalla chica, iPhone SE) "Ajustes" —donde vive el plan y el
   cobro— se quedaba fuera de cuadro, detrás de una barra que no parece
   arrastrable. Con `flex:1` cada pestaña se lleva exactamente un quinto del
   ancho y el texto se ajusta con el viewport, así que las cinco caben desde
   320px para arriba sin que nadie tenga que arrastrar nada.

   El corte va en 500px y no en 420px porque a su ancho natural las cinco
   piden ~474px: en un iPhone Pro Max (430px) la barra seguía desbordándose,
   que es justo el teléfono donde se vio el problema. */
@media (max-width: 500px) {
  .tabs { padding: 0 8px; gap: 0; }
  .tab { padding: 12px 7px; font-size: 13px; }
  /* Sólo donde las pestañas son cinco y tienen que caber TODAS: esta hoja la
     comparten los cuatro paneles, y el de admin trae siete — repartirle el
     ancho en partes iguales le parte los títulos en varios renglones y,
     como la barra es pegajosa, le tapa media pantalla. Ése sí se arrastra. */
  .tabs-fit { padding: 0 6px; overflow-x: visible; background: var(--surface); }
  .tabs-fit .tab {
    flex: 1 1 0; min-width: 0;
    padding: 12px 2px;
    font-size: clamp(11.5px, 3.4vw, 13px);
    letter-spacing: -.01em;
  }
}

main { max-width: 1080px; margin: 0 auto; padding: 24px 20px 60px; }
/* Los tres <select> de cumpleaños comparten un solo renglón: con el padding
   normal de campo (12px por lado) más la flecha nativa, "Año" salía cortado
   ("Añc"). Se aprieta sólo aquí. */
#n-birth-day, #n-birth-month, #n-birth-year,
#s-birth-day, #s-birth-month, #s-birth-year,
#reg-birth-day, #reg-birth-month, #reg-birth-year { padding-left: 8px; padding-right: 6px; }


/* Título de cada vista para quien navega con lector de pantalla: las cinco
   pestañas arrancaban en <h2>, sin un solo <h1> que dijera dónde estás. No se
   ve (el nombre de la vista ya está en la pestaña activa) y por eso tampoco
   cambia el diseño. `clip-path` además del `clip` viejo: el primero es el
   estándar, el segundo el respaldo. */
.sr-only {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
  overflow: hidden; white-space: nowrap; border: 0;
  clip: rect(0 0 0 0); clip-path: inset(50%);
}

/* ---------- Componentes ---------- */
.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 20px;
  box-shadow: var(--shadow);
}
.card + .card { margin-top: 16px; }
.card h2 { margin: 0 0 4px; font-size: 16px; }
.card .hint { margin: 0 0 16px; color: var(--muted); font-size: 13px; }

/* ---------- Tarjeta-acordeón ----------
   Progressive disclosure: en Análisis y en Ajustes hay más bloques de los que
   caben antes del primer scroll, y casi todos se consultan de vez en cuando.
   Plegados caben todos en una pantalla; el que se abre es el que pide sus
   datos (ver ACC_LOADERS en app.js). Es <details> nativo: el teclado, el
   lector de pantalla y el "buscar en la página" del navegador ya lo entienden
   sin que nosotros programemos nada. */
.card.acc { padding: 0; }
.card.acc > summary {
  display: flex; align-items: center; justify-content: space-between; gap: 14px;
  padding: 16px 20px;            /* 44 px de alto mínimo para el pulgar */
  min-height: 44px;
  cursor: pointer;
  list-style: none;
}
.card.acc > summary::-webkit-details-marker { display: none; }
.card.acc > summary::after {
  content: ''; flex: none;
  width: 9px; height: 9px;
  border-right: 2px solid var(--muted); border-bottom: 2px solid var(--muted);
  transform: rotate(45deg) translate(-2px, -2px);
}
.card.acc[open] > summary::after { transform: rotate(-135deg) translate(-2px, -2px); }
.card.acc > summary:hover { background: var(--bg); }
.card.acc > summary h2, .card.acc > summary h3 { margin: 0; font-size: 16px; }
/* El título se queda con el espacio libre: la pastilla de estado (p.ej. la de
   Recordatorios) se va junto a la flecha, no a media tarjeta. */
.card.acc > summary > div:first-child { flex: 1; }
.card.acc > summary .hint { margin: 2px 0 0; }
.card.acc[open] > summary { border-bottom: 1px solid var(--border); }
.card.acc .acc-body { padding: 16px 20px 20px; }
/* Anidado dentro de otra tarjeta (Funciones dentro de Tu plan, Cómo va dentro
   de Gamificación): sin caja propia, como los <details class="upl"> de Marca. */
.card > .acc, .card > .acc + .acc {
  border: 0; border-top: 1px solid var(--border);
  border-radius: 0; box-shadow: none;
  background: none;
  margin: 12px 0 0;
}
.card > .acc > summary { padding: 12px 0; }
.card > .acc > summary:hover { background: none; color: var(--brand-deep); }
.card > .acc[open] > summary { border-bottom: 0; }
.card > .acc .acc-body { padding: 0 0 12px; }
@media (prefers-reduced-motion: no-preference) {
  .card.acc > summary::after { transition: transform .2s ease; }
}

/* Título de pestaña (fuera de tarjeta): el "Resumen del negocio" de Análisis. */
.view-title { margin: 0 0 4px; font-size: 18px; }
.view-title + .hint { margin: 0 0 16px; color: var(--muted); font-size: 13px; font-weight: 300; }

.grid { display: grid; gap: 14px; }
/* #stats-grid (Análisis) es un .grid suelto, no un .card: la regla
   ".card + .card" de arriba no lo alcanza, así que sin este margen la
   siguiente tarjeta (acordeón "Puntos propios" / "Puntos Muuch") queda
   pegada encima, sin el mismo respiro que hay entre el resto de tarjetas. */
.grid-stats { grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); margin-bottom: 16px; }
.grid-form { grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); }

.stat { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 16px; overflow: hidden; }
/* Encabezado de bloque dentro de la cuadrícula de cifras: ocupa el renglón
   completo para que las tarjetas de abajo se lean como SUYAS. Ver
   renderStatsGrid() — sin esto, un negocio con varios tipos de programa
   mostraba 19 números seguidos sin dueño. */
.stat-group {
  grid-column: 1 / -1;
  margin: 10px 0 -4px;
  font-family: var(--font-title); font-size: 12px; font-weight: 700;
  letter-spacing: .06em; text-transform: uppercase; color: var(--muted);
}
.stat-group:first-child { margin-top: 0; }
/* overflow-wrap: una palabra sola larga (p.ej. "Principiante", nombre de
   escalón) no tiene dónde partirse por default y se sale de la tarjeta en
   vez de bajar de línea — esto la obliga a envolver antes de desbordar. */
.stat .n { font-size: 26px; font-weight: 680; letter-spacing: -.02em; overflow-wrap: break-word; }
.stat .l { font-size: 13px; color: var(--muted); margin-top: 2px; }

label { display: block; font-size: 13px; font-weight: 550; margin-bottom: 6px; }
/* Un <input> sin `type` es un campo de texto igual que los demás, pero sin
   estar en esta lista se quedaba con su ancho intrínseco (~20 caracteres)
   mientras el de al lado ocupaba todo el renglón: parecía un formulario a
   medio hacer, y así estaba el alta de vendedores del panel de Socios con
   seis campos angostos. Al vivir aquí, en la hoja que comparten los cuatro
   paneles, ningún formulario nuevo vuelve a nacer con el defecto por olvidar
   un atributo.
   `:where()` y no `:not()` a secas: :where() no suma especificidad, así que
   esto se queda como un default de nivel elemento y cualquier clase lo puede
   pisar. Con `input:not([type])` (0,1,1) la regla le habría ganado incluso a
   .stepup-digit — que son seis cuadritos de 42px para el código de acceso —
   y los habría estirado al ancho completo. */
input[type=text], input[type=email], input[type=password], input[type=number],
input[type=url], input[type=tel], input[type=search], input[type=date],
input:where(:not([type])),
select, textarea {
  width: 100%;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: 9px;
  background: #fff;
  outline: none;
}
input:focus, select:focus, textarea:focus { border-color: var(--brand); box-shadow: 0 0 0 3px color-mix(in srgb, var(--brand) 18%, transparent); }
input[type=color] { width: 54px; height: 40px; padding: 3px; border: 1px solid var(--border); border-radius: 9px; background: #fff; }
.field + .field { margin-top: 14px; }

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  padding: 10px 16px;
  min-height: 44px;
  border: 1px solid transparent;
  border-radius: 9px;
  background: var(--brand-deep); color: var(--brand-ink);
  font-weight: 600; cursor: pointer; text-decoration: none;
  transition: filter .12s ease, transform .08s ease;
}
.btn:hover:not(:disabled) { filter: brightness(1.07); }
/* Feedback táctil: se "hunde" al tocar, para que se sienta como un botón real. */
.btn:active:not(:disabled) { transform: scale(.96); filter: brightness(.93); }
.btn:disabled { opacity: .55; cursor: not-allowed; }
.btn-block { width: 100%; }
.btn-ghost { background: #fff; color: var(--text); border-color: var(--border); }
.btn-sm { padding: 6px 11px; font-size: 13px; font-weight: 550; min-height: 36px; }
.link-btn { background: none; border: 0; color: var(--brand-deep); font-weight: 600; font-size: 13px; cursor: pointer; padding: 0; width: auto; min-height: 0; }

/* Botón "Escanear QR": segundo en importancia después de buscar a mano. */
.btn-scan { background: var(--navy); }

/* Botones grandes de la caja: pensados para tablet, se aciertan sin mirar */
.action-buttons { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; margin-top: 18px; }
.btn-huge {
  padding: 26px 18px;
  font-size: 18px; font-weight: 680;
  border-radius: 14px;
  flex-direction: column; gap: 4px;
  min-height: 104px;
}
.btn-huge small { font-weight: 500; font-size: 13px; opacity: .85; }
.btn-redeem { background: var(--navy); }
/* Con el premio listo se intercambian los pesos visuales: el botón de marca
   pasa a "Canjear Premio" y sellar queda en segundo plano. Ver
   renderQuickCard() en app.js. */
.btn-redeem.btn-primary-swap { background: var(--brand-deep); }
.btn-huge.btn-secondary-swap { background: var(--navy); }
/* El orden visual manda: lo primero que se ve —y se aprieta— es la acción que
   toca. Iba sólo abajo de 560px, cuando los botones se apilan; pero el aparato
   para el que están pensados es la tablet del mostrador (ver el comentario de
   .btn-huge), y ahí quedaban lado a lado intercambiando NADA MÁS el color.
   Quien atiende en hora pico lleva nueve visitas apretando el de la izquierda:
   en la décima, el de la izquierda es el equivocado y el canje no se deshace.
   Con esto, la acción correcta ocupa siempre el mismo lugar que la mano
   ya busca. */
.action-buttons .btn-primary-swap { order: -1; }
@media (max-width: 560px) {
  .action-buttons { grid-template-columns: 1fr; }
}

/* ---------- Clientes recientes (caja) ---------- */
.recent-row {
  display: flex; align-items: center; gap: 12px;
  padding: 12px 2px;
  border-bottom: 1px solid var(--border);
  cursor: pointer;
}
.recent-row:last-child { border-bottom: 0; }
.recent-row:active { background: var(--bg); }
.recent-row .ri-name { font-weight: 600; font-size: 14px; }
.recent-row .ri-meta { font-size: 12px; color: var(--muted); margin-top: 1px; }
.btn-plus1 {
  flex: none; width: 44px; height: 44px; min-height: 44px;
  padding: 0; border-radius: 50%;
  font-size: 18px; font-weight: 700;
}
.btn-plus1.done { background: var(--ok); }
/* Cliente con el premio ya ganado: el "+1" sigue ahí (el sello extra cuenta
   para su siguiente tarjeta) pero deja de gritar, para que el ojo se vaya a
   la pill de "premio listo" y se abra su tarjeta a canjear. */
.btn-plus1-muted { background: var(--navy); opacity: .55; }
.recent-row .ri-name .pill { margin-left: 6px; vertical-align: 1px; }

/* ---------- Escanear QR (cámara de pantalla completa) ---------- */
.scan-overlay {
  position: fixed; inset: 0; z-index: 70;
  background: #000;
  display: flex; align-items: center; justify-content: center;
  overflow: hidden;
}
.scan-overlay video {
  position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
}
.scan-frame {
  position: relative; z-index: 1;
  width: min(68vw, 280px); aspect-ratio: 1;
  border: 3px solid #fff; border-radius: 20px;
  box-shadow: 0 0 0 9999px rgba(0, 0, 0, .45);
}
.scan-topbar {
  position: absolute; z-index: 2;
  top: max(env(safe-area-inset-top, 0px), 14px);
  right: 14px;
}
.scan-cancel { background: rgba(255, 255, 255, .16); color: #fff; border-color: rgba(255, 255, 255, .3); }
.scan-hint {
  position: absolute; z-index: 2;
  bottom: max(env(safe-area-inset-bottom, 0px), 24px);
  left: 20px; right: 20px;
  margin: 0; text-align: center;
  color: #fff; font-size: 15px; font-weight: 550;
  text-shadow: 0 1px 3px rgba(0, 0, 0, .6);
}

/* Tablas con varias columnas y/o botones que no se achican (Cliente/Progreso,
   historial de pagos): en pantalla angosta desbordaban la tarjeta — las
   últimas columnas quedaban recortadas fuera del margen en vez de scrollear.
   #programs-table NO va aquí a propósito: es un `.cardgrid` (grid de
   tarjetas que se reacomoda solo, no una tabla) y overflow-x:auto en un
   contenedor sin overflow-y explícito también vuelve automático el eje Y —
   eso recortaba el menú "..." de cada tarjeta (position:absolute, ver
   .menu-dd más abajo) en vez de dejarlo flotar libre, y para verlo completo
   había que scrollear el contenedor hacia abajo. */
#customers-table, #locations-table, #payments-history, #rw-top-clients, #rw-history, #programs-points-summary { overflow-x: auto; }

table { width: 100%; border-collapse: collapse; font-size: 14px; }
th { text-align: left; font-size: 12px; text-transform: uppercase; letter-spacing: .03em; color: var(--muted); padding: 8px 10px; border-bottom: 1px solid var(--border); }
td { padding: 11px 10px; border-bottom: 1px solid var(--border); }
tr:last-child td { border-bottom: 0; }
.row-hover:hover { background: #fafbfc; cursor: pointer; }
/* Columnas de dinero (Historial de pagos): alinea el número, no el texto. */
.num { text-align: right; }

/* ---------- Filtros de Clientes ----------
   Eran un `flex-wrap` con `align-items:flex-end`: en un teléfono los dos
   campos de fecha caían lado a lado a ~165 px cada uno, con 10 px de aire
   entre ellos y la etiqueta pegada encima de la caja — se leían como dos
   cuadros encimados. Un grid con ancho mínimo REAL los pone en dos columnas
   sólo cuando de verdad caben (240 px cada uno) y en una sola cuando no.
   El pie va aparte porque la casilla y el botón de CSV no son campos: no
   tienen por qué competir por hueco con las fechas. */
.filter-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(240px, 100%), 1fr));
  gap: 14px 12px;
  align-items: end;
}
/* Dentro del grid cada campo ya trae su propia celda: el margen entre
   campos apilados sobraría y descuadraría la línea base de las etiquetas. */
.filter-grid .field { margin: 0; }
.filter-foot {
  display: flex; flex-wrap: wrap; align-items: center;
  justify-content: space-between; gap: 12px;
}

/* ---------- Lista de Clientes: una ficha por PERSONA ----------
   Antes era una <table> de seis columnas que por debajo de 700px se apilaba
   en fichas de "ETIQUETA — valor": ~250 px de alto por cliente, y quien
   estaba en tres programas aparecía tres veces (una fila por tarjeta). Ahora
   el servidor agrupa por persona y cada ficha son tres renglones: quién es,
   cómo localizarlo y cuándo vino, y una pastilla por tarjeta. ~95 px, y las
   mismas tres personas ocupan lo que antes ocupaba una. */
.cust {
  border: 1px solid var(--border); border-radius: 12px;
  padding: 11px 13px; margin-bottom: 8px;
  cursor: pointer;
  transition: border-color .12s ease, background .12s ease;
}
.cust:last-child { margin-bottom: 0; }
.cust:hover { border-color: color-mix(in srgb, var(--brand) 45%, var(--border)); }
.cust:active { background: var(--bg); }
.cust-off { opacity: .6; }
.cust-head { display: flex; align-items: flex-start; gap: 10px; }
.cust-who { flex: 1; min-width: 0; }
.cust-name {
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
  font-weight: 650; font-size: 15px; line-height: 1.3;
}
.cust-sub {
  color: var(--muted); font-size: 13px; line-height: 1.4; margin-top: 1px;
  /* El teléfono no se parte a media cifra en una pantalla angosta. */
  overflow-wrap: anywhere;
}
/* "Desactivar" es la acción rara de esta pantalla (a un cliente se entra
   para VERLO): va callada dentro del menú "…" (mismo .menu-dd ya probado en
   la tabla de programas), no como botón suelto a la vista. */
.cust-head .menu-dd summary {
  flex: none;
  background: transparent; border-color: transparent; color: var(--muted);
  font-size: 12px; font-weight: 550; padding: 5px 8px; min-height: 32px;
  border-radius: 8px;
}
.cust-head .menu-dd summary:hover { background: #fff; border-color: var(--border); color: var(--text); }
/* La fila entera ya abre el detalle del cliente; el chevron lo dice sin
   robarle sitio al menú "…" (handoff 57, punto 5.2). */
.cust-chevron { flex: none; color: var(--muted); font-size: 18px; line-height: 1; align-self: center; }
.cust-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }

/* Una pastilla por tarjeta: "Casa Flamingo · 3 de 5 sellos". Es un <button>
   porque abre ESA tarjeta, no la principal. */
.chip {
  display: inline-flex; align-items: center;
  max-width: 100%;
  padding: 6px 11px;
  border: 1px solid var(--border); border-radius: 99px;
  background: var(--bg); color: var(--text);
  font: inherit; font-size: 12px; font-weight: 550; line-height: 1.4;
  /* 32px de alto: el dedo la acierta sin mirar. La ficha entera también es
     tocable (abre la tarjeta principal), así que la pastilla es un atajo,
     no el único camino — por eso no necesita los 44px de una acción sola. */
  min-height: 32px; cursor: pointer; text-align: left;
}
.chip:hover { border-color: color-mix(in srgb, var(--brand) 55%, var(--border)); }
.chip-ok    { background: var(--ok-bg);   color: var(--ok);    border-color: transparent; }
.chip-warn  { background: var(--warn-bg); color: var(--warn);  border-color: transparent; }
.chip-muted { color: var(--muted); }
/* Los Puntos Muuch son de la red, no de este comercio: color propio para que
   no se confundan con el avance de una tarjeta suya. */
.chip-muuch { background: #faf5ff; color: #7c3aed; border-color: #e9d5ff; cursor: default; }
.chip-muuch:hover { border-color: #e9d5ff; }

.pill { display: inline-block; padding: 3px 9px; border-radius: 99px; font-size: 12px; font-weight: 600; }
.pill-ok { background: var(--ok-bg); color: var(--ok); }
.pill-warn { background: var(--warn-bg); color: var(--warn); }
.pill-muted { background: #f2f4f7; color: var(--muted); }
/* Tono propio para "recomendado": pill-warn es el mismo ámbar que "bloqueado
   por tu plan" y el aviso de CLABE/Excel — una recomendación no es una
   advertencia, y compartir el token hacía leer la una con la cautela de la
   otra (revisión de diseño, handoff 71 bloque 1). */
.pill-reco { background: color-mix(in srgb, var(--brand) 14%, white); color: var(--brand-deep); }

/* Selector de tipo de programa ("Agregar programa") */
.type-picker { display: flex; flex-direction: column; gap: 8px; margin-bottom: 18px; }
.type-option {
  display: flex; align-items: center; gap: 12px; width: 100%; text-align: left;
  padding: 12px 14px; border: 1px solid var(--border); border-radius: 9px;
  background: #fff; cursor: pointer; font: inherit;
}
.type-option[aria-pressed="true"] { border-color: var(--brand); background: color-mix(in srgb, var(--brand) 8%, white); }
.type-option:disabled { cursor: not-allowed; color: var(--muted); }
/* Un tipo "ya en uso" es SIEMPRE disabled (ver typeOptionsHtml en app.js). Si
   además es el que traía aria-pressed (p. ej. el comercio ya tiene los 6
   tipos activos y no queda ningún default disponible), el tinte de "elegido"
   encima de un botón que no se puede elegir manda una señal contradictoria
   — se ve seleccionado y bloqueado a la vez. Vuelve al aspecto neutro. */
.type-option:disabled[aria-pressed="true"] { border-color: var(--border); background: #fff; }
.type-option:disabled .type-info strong { color: var(--muted); }
.type-icon { font-size: 20px; line-height: 1; }
.type-info { display: flex; flex-direction: column; gap: 1px; flex: 1; }
.type-info strong { font-size: 14px; }
.type-info span { font-size: 12px; color: var(--muted); }
.type-option .pill { margin-left: auto; flex-shrink: 0; }

/* Progreso de sellos */
.dots { display: flex; gap: 4px; flex-wrap: wrap; }
.dot { width: 13px; height: 13px; border-radius: 50%; border: 1.5px solid var(--border); background: #fff; }
.dot.on { background: var(--brand); border-color: var(--brand); }

.alert { padding: 12px 14px; border-radius: 9px; font-size: 14px; margin-bottom: 14px; }
.alert-error { background: var(--danger-bg); color: var(--danger); }
.alert-ok { background: var(--ok-bg); color: var(--ok); }
.alert-warn { background: var(--warn-bg); color: var(--warn); }

.toast {
  position: fixed; left: 50%; bottom: 26px; transform: translateX(-50%) translateY(16px);
  /* Anclado en left:50%, el ancho que puede tomar solo es la MITAD de la
     pantalla: en el teléfono cualquier aviso de más de tres palabras se
     partía en tres renglones angostos. width:max-content lo suelta y el
     max-width lo mantiene dentro de la pantalla con aire a los lados. */
  width: max-content; max-width: min(520px, calc(100vw - 32px)); text-align: center;
  background: var(--navy); color: #fff;
  padding: 13px 20px; border-radius: 10px;
  font-weight: 550; font-size: 15px;
  box-shadow: 0 8px 24px rgba(16,24,40,.24);
  opacity: 0; pointer-events: none;
  transition: opacity .18s ease, transform .18s ease;
  /* El `max-width: 90vw` que vivía aquí pisaba al de arriba (era la MISMA
     propiedad, declarada después) y dejaba sin efecto el tope de 520px que
     ese comentario explica: en un monitor ancho un aviso de tres palabras
     podía estirarse 1700px. Se quedó sólo la primera declaración. */
  z-index: 60;
}
.toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }
.toast.ok { background: #067647; }
.toast.err { background: #b42318; }

.muted { color: var(--muted); }
.empty { text-align: center; padding: 36px 16px; color: var(--muted); }
.row { display: flex; gap: 10px; align-items: center; }
.mt { margin-top: 16px; }

.usage-bar { height: 6px; background: #f2f4f7; border-radius: 99px; overflow: hidden; margin-top: 8px; }
.usage-bar > i { display: block; height: 100%; background: var(--brand); }
.usage-bar.over > i { background: var(--danger); }

/* Mapa de calor (Clientes → "Cuándo vienen tus clientes"): 7 columnas de día,
   una fila por bloque de horario — sequential de un solo hue, ver skill dataviz. */
.heatmap { display: flex; flex-direction: column; gap: 4px; overflow-x: auto; }
.heat-row { display: grid; grid-template-columns: 62px repeat(7, minmax(24px, 1fr)); gap: 4px; align-items: center; }
.heat-header { font-size: 11px; color: var(--muted); text-align: center; }
/* Nombre del bloque + horario en su propia línea — a como texto plano en
   una sola línea ("Madrugada (0–6)") no cabía en columna angosta de
   celular y quedaba cortado. */
.heat-label { display: flex; flex-direction: column; font-size: 12px; line-height: 1.2; color: var(--muted); }
.heat-label-hours { font-size: 10px; opacity: .75; }
.heat-cell {
  aspect-ratio: 1; border-radius: 6px; display: grid; place-items: center;
  font-size: 11px; font-weight: 600; color: var(--navy);
}

/* Barras horizontales de edad/género (Clientes → "Quiénes son tus clientes"). */
.demo-bars { display: flex; flex-direction: column; gap: 8px; }
.demo-bar-row { display: grid; grid-template-columns: 60px 1fr 28px; gap: 10px; align-items: center; }
.demo-bar-label { font-size: 13px; color: var(--muted); }
.demo-bar-track { height: 10px; background: #f2f4f7; border-radius: 99px; overflow: hidden; }
.demo-bar-track > i { display: block; height: 100%; border-radius: 99px; }
.demo-bar-count { font-size: 12px; color: var(--muted); text-align: right; }

/* ---------------------------------------------------------------- Planes */
.oxxo-breakdown { text-align: right; font-size: 12px; max-width: 220px; }

/* ---------------------------------------------------------- mini-tarjeta */
.card-preview {
  border-radius: 20px; padding: 22px; color: #fff;
  background: var(--preview-brand, var(--brand));
  box-shadow: 0 10px 30px rgba(16,24,40,.18);
  max-width: 340px; margin: 0 auto;
}
.card-preview .cp-top { display: flex; align-items: center; gap: 10px; margin-bottom: 18px; }
.card-preview .cp-logo {
  width: 42px; height: 42px; border-radius: 12px; background: rgba(255,255,255,.22);
  display: grid; place-items: center; font-weight: 700; font-size: 18px; overflow: hidden; flex-shrink: 0;
}
.card-preview .cp-logo img { width: 100%; height: 100%; object-fit: cover; }
.card-preview .cp-name { font-weight: 700; font-size: 16px; line-height: 1.2; }
.card-preview .cp-dots { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 18px; }
.card-preview .cp-dot {
  width: 26px; height: 26px; border-radius: 50%; border: 2px solid rgba(255,255,255,.5);
  display: grid; place-items: center; font-size: 12px; background: transparent;
}
.card-preview .cp-dot.on { background: #fff; border-color: #fff; color: var(--preview-brand, var(--brand)); }
.card-preview .cp-dot img { width: 18px; height: 18px; object-fit: contain; }
.card-preview .cp-stats { font-size: 12px; opacity: .8; letter-spacing: .02em; text-transform: uppercase; margin-bottom: 10px; }
.card-preview .cp-reward { font-size: 14px; opacity: .92; text-align: center; padding-top: 10px; border-top: 1px solid rgba(255,255,255,.25); }

/* Con cualquier overlay abierto (editor de programa, panel de cliente,
   wizard, verificación reforzada), el body deja de scrollear — antes nada lo
   bloqueaba: en escritorio, al llegar al límite del scroll DENTRO del
   modal, la página de fondo se seguía moviendo. Ver lockBodyScroll() en
   app.js (cuenta overlays abiertos, por si uno se abre encima de otro). */
body.body-scroll-locked { overflow: hidden; }

/* ---------------------------------------------------------- wizard */
.wizard-overlay, .editor-overlay {
  position: fixed; inset: 0; background: rgba(16,24,40,.6); z-index: 200;
  display: flex; align-items: center; justify-content: center; padding: 16px;
}
.wizard-box, .editor-box {
  background: #fff; border-radius: 20px; width: 100%; max-width: 880px; max-height: 92vh;
  display: flex; flex-direction: column; overflow: hidden; box-shadow: 0 24px 70px rgba(16,24,40,.3);
}
.wizard-steps { display: flex; gap: 8px; padding: 18px 24px 0; }
.wizard-steps .ws-step { flex: 1; height: 4px; border-radius: 99px; background: #eef0f4; }
.wizard-steps .ws-step.active, .wizard-steps .ws-step.done { background: var(--brand); }
.wizard-body { display: grid; grid-template-columns: 1fr 1fr; gap: 28px; padding: 24px; overflow-y: auto; }
.wizard-form h2 { margin-top: 0; }
.wizard-preview { display: flex; align-items: flex-start; justify-content: center; padding-top: 8px; }
.wizard-footer { display: flex; justify-content: space-between; gap: 10px; padding: 16px 24px; border-top: 1px solid var(--border); }
@media (max-width: 640px) {
  .wizard-body { grid-template-columns: 1fr; }
  .wizard-preview { order: -1; }
}

/* ------------------------------------------- Verificación reforzada (step-up) */
.stepup-overlay {
  position: fixed; inset: 0; z-index: 210;
  display: flex; align-items: center; justify-content: center; padding: 16px;
  background: rgba(16, 24, 40, 0);
  transition: background .22s cubic-bezier(.22, 1, .36, 1);
}
.stepup-overlay.show { background: rgba(16, 24, 40, .55); backdrop-filter: blur(3px); }
.stepup-box {
  position: relative;
  width: 100%; max-width: 380px;
  background: var(--surface); border-radius: 20px;
  padding: 28px 24px 24px;
  box-shadow: 0 24px 70px rgba(16, 24, 40, .3);
  text-align: center;
  /* Mismo candado que .modal-box del panel de ventas: un overlay `fixed`
     centrado con una caja más alta que la ventana se corta por arriba Y por
     abajo, sin forma de llegar al contenido. Aquí lo de abajo es el campo
     del código y el botón de reenviar, o sea que el dueño se quedaría sin
     poder completar la verificación — justo en el candado de seguridad.
     Pasa en cuanto la ventana es baja: un teléfono en horizontal, o el
     teclado abierto sobre una pantalla chica. Dos veces max-height a
     propósito: dvh descuenta las barras del navegador móvil y el vh es el
     respaldo para quien no lo entienda; los 32px son el padding del overlay. */
  max-height: calc(100vh - 32px);
  max-height: calc(100dvh - 32px);
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  /* Entra "materializándose" (escala + opacidad juntas, no sólo un fade) desde
     abajo del punto de reposo — mismo criterio que un bottom-sheet de iOS. */
  opacity: 0; transform: translateY(18px) scale(.97);
  transition: opacity .22s cubic-bezier(.22, 1, .36, 1), transform .22s cubic-bezier(.22, 1, .36, 1);
}
.stepup-overlay.show .stepup-box { opacity: 1; transform: translateY(0) scale(1); }
@media (prefers-reduced-motion: reduce) {
  .stepup-box, .stepup-overlay { transition: opacity .15s linear !important; transform: none !important; }
}
.stepup-close {
  position: absolute; top: 12px; right: 12px;
  width: 30px; height: 30px; border-radius: 50%; border: 0;
  background: var(--bg); color: var(--muted); cursor: pointer; font-size: 14px;
  display: grid; place-items: center;
  transition: transform .1s ease, background .15s ease;
}
.stepup-close:active { transform: scale(.9); background: var(--border); }
.stepup-icon { font-size: 32px; line-height: 1; margin-bottom: 4px; }
.stepup-box h2 { margin: 4px 0 8px; font-size: 19px; }
.stepup-box .hint { margin: 0 0 4px; }
.stepup-box .alert { text-align: left; }
.stepup-box .btn { width: 100%; }

/* -------------------------------------------------- confirmModal() genérico */
/* Reemplazo de confirm() nativo — mismo candado y misma animación de entrada
   que .stepup-box de arriba (portado del panel de admin, ver confirmModal()
   en app.js), para que el diálogo se vea del producto y no de Chrome. */
.modal-overlay {
  position: fixed; inset: 0; z-index: 210;
  display: flex; align-items: center; justify-content: center; padding: 16px;
  background: rgba(16, 24, 40, 0);
  transition: background .22s cubic-bezier(.22, 1, .36, 1);
}
.modal-overlay.show { background: rgba(16, 24, 40, .55); backdrop-filter: blur(3px); }
.modal-box {
  width: 100%; max-width: 380px;
  background: var(--surface); border-radius: 20px;
  padding: 28px 24px 24px;
  box-shadow: 0 24px 70px rgba(16, 24, 40, .3);
  text-align: center;
  max-height: calc(100vh - 32px);
  max-height: calc(100dvh - 32px);
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  opacity: 0; transform: translateY(18px) scale(.97);
  transition: opacity .22s cubic-bezier(.22, 1, .36, 1), transform .22s cubic-bezier(.22, 1, .36, 1);
}
.modal-overlay.show .modal-box { opacity: 1; transform: translateY(0) scale(1); }
@media (prefers-reduced-motion: reduce) {
  .modal-box, .modal-overlay { transition: opacity .15s linear !important; transform: none !important; }
}
.modal-box h2 { margin: 4px 0 8px; font-size: 19px; }
.modal-box .hint { margin: 0 0 4px; }

.stepup-code { display: flex; gap: 8px; justify-content: center; margin: 18px 0 6px; }
.stepup-digit {
  width: 42px; height: 52px; text-align: center; font-size: 22px; font-weight: 700;
  border: 1.5px solid var(--border); border-radius: 10px;
  font-variant-numeric: tabular-nums;
  /* Responde en cuanto se toca (borde de foco al instante), no hasta que se suelta. */
  transition: border-color .1s ease, box-shadow .1s ease;
}
.stepup-digit:focus { border-color: var(--brand); box-shadow: 0 0 0 3px color-mix(in srgb, var(--brand) 18%, transparent); }

/* ------------------------------------------- Marca (Tarjetas): form + preview del Wallet */
.brand-grid { grid-template-columns: 1fr auto; gap: 20px; align-items: start; }
@media (max-width: 640px) { .brand-grid { grid-template-columns: 1fr; } }

/* ------------------------------------------- tipos de programa (wizard + Configuración) */
.type-picker { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
@media (max-width: 640px) {
  .type-picker { grid-template-columns: 1fr; }
  /* En 390px, elegido el tipo, las otras 5 tarjetas sólo empujan el
     formulario real (~1200px de scroll antes del primer campo). Con
     .has-selection se colapsa a la elegida + "Cambiar tipo"; en escritorio
     esta regla no aplica y el catálogo completo se ve siempre (handoff 57,
     5.3). */
  .type-picker.has-selection .type-option[aria-pressed="false"] { display: none; }
  .type-picker.has-selection .type-picker-change { display: inline-flex; margin-top: 2px; }
}
.type-picker-change { display: none; }
.type-option { align-items: flex-start; }
.type-option .type-ico {
  width: 36px; height: 36px; border-radius: 10px; flex-shrink: 0;
  background: var(--bg); color: var(--brand-deep); display: grid; place-items: center;
}
.type-option[aria-pressed="true"] .type-ico { background: #fff; }
.type-option .type-info .pill { margin: 6px 0 0; align-self: flex-start; }
.type-option[data-locked="true"] { border-style: dashed; }
.type-option:hover { border-color: #cfd4dc; }

/* ------------------------------------------- planes (paso del wizard + pestaña Plan) */
/* flex + wrap + centrado, no grid de columnas fijas: con 5 planes en el
   catálogo (antes eran 3), un grid de 3 columnas deja la fila 2 con 2
   tarjetas pegadas a la izquierda y un hueco a la derecha — se ve
   inacabado. Flex con `justify-content:center` centra esa fila sobrante
   sola, sin media queries por cantidad de planes (revisión de diseño,
   handoff 71 bloque 1). */
.plan-cards { display: flex; flex-wrap: wrap; justify-content: center; gap: 12px; margin-top: 14px; }
.plan-card {
  position: relative; border: 1px solid var(--border); border-radius: 14px; padding: 16px;
  background: var(--surface); display: grid; gap: 8px; align-content: start;
  flex: 1 1 220px; max-width: 300px;
}
.plan-card.reco { border-color: var(--brand); box-shadow: 0 0 0 1px var(--brand); }
.plan-card .plan-tag { position: absolute; top: -11px; left: 14px; }
.plan-card h3 { margin: 0; font-size: 17px; }
.plan-card .plan-price { font-family: var(--font-title); font-size: 26px; font-weight: 800; letter-spacing: -.02em; color: var(--navy); font-variant-numeric: tabular-nums; }
.plan-card .plan-price small { font-family: inherit; font-size: 13px; font-weight: 600; color: var(--muted); letter-spacing: 0; }
/* Max es a cotización, no a precio: metida en la tipografía de $649 MXN se
   parte a dos líneas en negritas de 26px e infla su tarjeta frente a sus
   vecinas con número — se lee como un plan más caro, no como "hay que
   preguntar". Un tratamiento de etiqueta, del tamaño de un h3. */
.plan-card .plan-price.is-quote { font-size: 17px; font-weight: 700; letter-spacing: 0; color: var(--text); }
.plan-card ul { margin: 0; padding-left: 18px; font-size: 13px; display: grid; gap: 4px; }
.plan-card .plan-limits { font-size: 12px; color: var(--muted); }
.plan-card .btn { justify-self: start; }
.seg { display: inline-flex; border: 1px solid var(--border); border-radius: 99px; padding: 3px; background: var(--surface); }
.seg button { border: 0; border-radius: 99px; padding: 6px 12px; background: transparent; color: var(--muted); font-weight: 600; font-size: 13px; cursor: pointer; }
.seg button[aria-pressed="true"] { background: var(--navy); color: #fff; }

/* ------------------------------------------- regreso de Stripe / cierre del wizard */
.spinner { width: 44px; height: 44px; border-radius: 50%; border: 4px solid var(--border); border-top-color: var(--brand); animation: spin 1s linear infinite; }
.checkmark { width: 44px; height: 44px; border-radius: 50%; background: var(--ok-bg); color: var(--ok); display: grid; place-items: center; font-weight: 800; font-size: 22px; }
@keyframes spin { to { transform: rotate(360deg); } }
.next-steps { display: grid; gap: 8px; margin-top: 8px; }
.next-steps a, .next-steps span { display: flex; gap: 10px; align-items: center; padding: 10px 12px; border: 1px solid var(--border); border-radius: 12px; background: var(--surface); color: var(--text); text-decoration: none; font-size: 14px; }
.next-steps a:hover { border-color: var(--brand); }
.wizard-foot-link,
/* Pagar en OXXO es la alternativa, no la ruta principal: va como enlace
   debajo del botón del plan, no como otro botón que compita con él. */
.pay-alt { background: none; border: 0; color: var(--muted); text-decoration: underline; text-underline-offset: 3px; cursor: pointer; padding: 0; }
/* justify-self porque .plan-card es grid: sin esto el enlace se estira de
   lado a lado de la tarjeta y el subrayado con él. */
.pay-alt { justify-self: start; font-size: 12.5px; }
.pay-alt:hover { color: var(--text); }

/* Estos tres son botones con cara de enlace, y por eso miden lo que mide su
   texto: 14-18px de alto. Con el dedo eso no se acierta — Apple pide 44pt y
   aquí hay acciones que importan ("Pagar en efectivo en OXXO", "Terminar
   después", "Reenviar código").
   Se agranda lo que se TOCA, no lo que se ve: un ::after invisible centrado
   que sube el alcance a 44px sin mover un pixel del texto ni empujar el
   layout (por eso ::after y no padding, que sí correría la tarjeta del plan).
   Sólo donde el puntero es grueso: con ratón el objetivo chico no estorba y
   no hay razón para agrandar áreas invisibles que puedan taparse entre sí. */
@media (pointer: coarse) {
  .pay-alt, .wizard-foot-link, .link-btn { position: relative; }
  .pay-alt::after, .wizard-foot-link::after, .link-btn::after {
    content: ''; position: absolute; left: 0; right: 0;
    top: 50%; transform: translateY(-50%);
    height: 44px; min-width: 44px;
  }
}
.wizard-steps .ws-step.skipped { background: var(--border); }
@media (prefers-reduced-motion: reduce) { .spinner { animation: none; } }

/* Fondo de tarjeta (plan Pro+): mismo tratamiento que la tarjeta pública (/c/:slug)
   — imagen a cover con un velo oscuro para que el texto blanco siga legible. */
/* La banda de la tarjeta, con la proporción EXACTA que le dan Apple (strip.png,
   375x123) y Google (heroImage, 1032x336). Antes esta imagen se pintaba como
   fondo de toda la tarjeta con un velo negro encima: eso no existe en ninguna
   de las dos wallets —una storeCard no tiene fondo— así que el comercio
   diseñaba contra una vista previa que su cliente nunca iba a ver. */
.card-preview .cp-strip {
  aspect-ratio: 1125 / 369;
  background-size: cover;
  background-position: center;
  border-radius: 10px;
  margin-bottom: 16px;
}
.card-preview .cp-powered { font-size: 10px; letter-spacing: .06em; text-transform: uppercase; opacity: .7; text-align: center; margin-top: 10px; }

/* ---------------------------------------------------------- bloque 1: hojas y panel de cliente */
.sheet { border: 1px solid var(--border); border-radius: 12px; background: var(--bg); padding: 12px 14px; }
.drawer-overlay { position: fixed; inset: 0; background: rgba(16, 24, 40, .45); z-index: 150; display: flex; justify-content: flex-end; }
.drawer {
  width: min(540px, 100%); height: 100%; background: var(--surface);
  box-shadow: -12px 0 40px rgba(16, 24, 40, .25); display: flex; flex-direction: column;
}
.drawer-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 10px; padding: 18px 20px 12px; border-bottom: 1px solid var(--border); }
.drawer-head h2 { margin: 0; font-size: 18px; }
.drawer-body { padding: 16px 20px 32px; overflow-y: auto; min-height: 0; flex: 1 1 auto; display: grid; gap: 18px; align-content: start; }
.drawer h3 { font-size: 12px; text-transform: uppercase; letter-spacing: .05em; color: var(--muted); margin: 0 0 8px; }
.kv { display: grid; grid-template-columns: 130px 1fr; gap: 6px 10px; font-size: 13px; }
.kv b { color: var(--muted); font-weight: 600; }
.tx-list > div { display: flex; gap: 10px; justify-content: space-between; align-items: baseline; padding: 8px 0; border-bottom: 1px solid var(--border); font-size: 13px; }
.tx-list > div:last-child { border-bottom: 0; }
@media (prefers-reduced-motion: no-preference) {
  .drawer { animation: drawer-in .18s ease-out; }
  @keyframes drawer-in { from { transform: translateX(24px); opacity: 0; } }
}

/* ---------------------------------------------------------- bloque 2: caja, tarjetas, hitos */
.greeting { margin: 0 0 14px; font-size: 15px; color: var(--muted); }
.greeting b { color: var(--text); }
.milestone {
  display: flex; gap: 12px; align-items: center; margin-bottom: 14px;
  border: 1px solid var(--brand); background: color-mix(in srgb, var(--brand) 8%, white);
  border-radius: 14px; padding: 12px 14px;
}
.milestone .ic { width: 38px; height: 38px; border-radius: 50%; background: var(--brand-deep); color: #fff; display: grid; place-items: center; font-family: var(--font-title); font-weight: 800; font-size: 14px; flex: none; }
.milestone b { display: block; font-family: var(--font-title); }
.milestone span { font-size: 13px; }
/* Cerrar el aviso era un objetivo de 20x23: el dedo le pegaba al aviso (que
   no hace nada) más veces que a la cruz. El área crece a 36x36 sin que la
   cruz se vea más grande — `margin: -6px 0` se come el aire que agrega. */
.milestone .x {
  margin-left: auto; margin-block: -6px;
  background: none; border: 0; color: var(--muted);
  font-size: 20px; line-height: 1; cursor: pointer;
  width: 36px; height: 36px; flex: none;
  display: grid; place-items: center; border-radius: 50%;
}
.milestone .x:hover { background: rgba(15, 23, 42, .06); color: var(--text); }
.milestone.quota { border-color: var(--warn); background: var(--warn-bg); }
.milestone.quota .ic { background: var(--warn); }
details > summary.btn { list-style: none; }
details > summary.btn::-webkit-details-marker { display: none; }
.cardgrid { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 14px; }
.pcard { border: 1px solid var(--border); border-radius: 14px; padding: 14px; display: grid; gap: 8px; background: var(--surface); align-content: start; }
.pcard .card-preview { max-width: 100%; padding: 16px; border-radius: 16px; }
.pcard.ghost { border-style: dashed; align-content: center; text-align: center; color: var(--muted); min-height: 160px; }
.pcard.ghost b { display: block; color: var(--text); }
.menu-dd { position: relative; }
.menu-dd summary { list-style: none; cursor: pointer; }
.menu-dd summary::-webkit-details-marker { display: none; }
.menu-dd .menu-list { position: absolute; right: 0; top: calc(100% + 4px); z-index: 30; min-width: 160px; background: var(--surface); border: 1px solid var(--border); border-radius: 10px; box-shadow: var(--shadow); padding: 4px; display: grid; }
.menu-dd .menu-list button { border: 0; background: none; text-align: left; padding: 8px 10px; border-radius: 7px; font-size: 13px; cursor: pointer; }
.menu-dd .menu-list button:hover { background: var(--bg); }
.upl { border-top: 1px solid var(--border); padding: 12px 0; }
.upl summary { cursor: pointer; font-size: 14px; }
.upl + .upl { margin-top: 0; }
.pill-muuch { background: #faf5ff; color: #7c3aed; }
input[type=range] { width: 100%; accent-color: #7c3aed; }
/* Editor de tarjetas en móvil: hoja a pantalla completa y una sola columna
   (con dos columnas el formulario desbordaba el ancho y el botón de guardar
   quedaba fuera del alcance del toque). */
@media (max-width: 640px) {
  .editor-overlay { padding: 0; align-items: stretch; }
  .editor-box { max-height: 100vh; border-radius: 0; }
  .editor-box .grid-form { grid-template-columns: 1fr; }
  .editor-box .drawer-body { overflow-x: hidden; }
}

/* ---- Gamificación (Fase 1) ---- */
.mech { border: 1px solid var(--border); border-radius: var(--radius); padding: 14px; background: var(--surface); }
.mech.on { border-color: var(--brand); box-shadow: inset 3px 0 0 var(--brand); }
.mech.locked { background: var(--bg); }
.switch { position: relative; display: inline-block; width: 44px; height: 26px; flex: none; }
.switch input { opacity: 0; width: 0; height: 0; position: absolute; }
.switch i { position: absolute; inset: 0; background: #d0d5dd; border-radius: 99px; transition: background .15s; cursor: pointer; }
.switch i::after { content: ''; position: absolute; width: 20px; height: 20px; left: 3px; top: 3px; background: #fff; border-radius: 50%; transition: transform .15s; box-shadow: 0 1px 2px rgba(0,0,0,.2); }
.switch input:checked + i { background: var(--brand); }
.switch input:checked + i::after { transform: translateX(18px); }
.switch input:focus-visible + i { outline: 2px solid var(--brand); outline-offset: 2px; }
.pill-gold { background: #fff7e6; color: #b45309; }
.gm-chart svg { width: 100%; height: auto; display: block; }
.gm-chart .lg { display: flex; gap: 14px; font-size: 12px; color: var(--muted); margin-top: 6px; }
.gm-chart .lg i { display: inline-block; width: 10px; height: 10px; border-radius: 2px; margin-right: 5px; vertical-align: -1px; }

/* Lada + número son UN campo, no dos: el borde y el foco viven en el
   contenedor y la lada va dentro como prefijo. Mismo criterio que el
   mini-sitio (public/c/index.html) — con dos cajas separadas, "Teléfono o
   correo" se leía como si pidiera dos datos distintos. */
.phone-row {
  display:flex; align-items:stretch; background:#fff;
  border:1px solid var(--border); border-radius:8px;
  transition:border-color 120ms ease, box-shadow 120ms ease;
}
.phone-row:focus-within {
  border-color:var(--brand); box-shadow:0 0 0 3px color-mix(in srgb, var(--brand) 18%, transparent);
}
.phone-row select, .phone-row input {
  border:0; border-radius:0; background-color:transparent; box-shadow:none;
}
.phone-row select {
  flex:0 0 auto; width:auto;
  padding-left:12px; padding-right:6px;
  color:var(--muted); font-variant-numeric:tabular-nums;
  border-right:1px solid var(--border);
}
.phone-row input { flex:1; min-width:0; }
/* Un correo no tiene lada. Se quita entera, sin animarla: un <select> no se
   recorta bien con overflow:hidden y a media transición se veía aplastado.
   Mismo criterio que el mini-sitio. */
.phone-row.is-email select { display:none; }
@media (prefers-reduced-motion: reduce) {
  .phone-row { transition:none; }
}

/* ------------------------------------------------------ Cerrar mi cuenta
   La única acción irreversible del panel. El tono es lo contrario de una
   alarma: nada rojo hasta que el dueño abre el panel y ve los números
   reales, y el rojo aparece sólo en el botón que de verdad cierra —
   pintar de rojo el que abre el panel sería mentir sobre lo que hace. */
.cierre-abrir { color:var(--danger); border-color:color-mix(in srgb, var(--danger) 30%, var(--border)); }
.cierre-abrir:hover:not(:disabled) { border-color:var(--danger); }

/* No existía ningún botón destructivo en el panel: los diálogos de borrar
   usaban el botón normal. Va DESPUÉS de .btn a propósito — misma
   especificidad, gana el último, y arriba el fondo de .btn se lo come. */
.btn-danger { background:var(--danger); }

/* El panel crece en su lugar en vez de abrir un modal: son siete líneas de
   consecuencias con cifras, y a eso hay que poder volver a mirarlo sin que
   una capa encima tape el resto de la pantalla. La altura se anima con
   grid-template-rows (0fr -> 1fr), que sí interpola, en vez de un
   max-height inventado que se pasa o se queda corto según el contenido. */
.cierre-panel { display:grid; grid-template-rows:0fr; transition:grid-template-rows .32s cubic-bezier(.22,1,.36,1); }
.cierre-panel[data-abierto="true"] { grid-template-rows:1fr; }
.cierre-panel > div { overflow:hidden; min-height:0; }
.cierre-cuerpo { padding-top:16px; }

/* Recibo, no tarjetas: cada línea es una consecuencia con su cifra a la
   derecha, alineada por tabular-nums para que los números formen columna. */
.cierre-lista { display:grid; gap:0; margin:0 0 16px; font-size:14px; }
.cierre-lista > li {
  display:flex; justify-content:space-between; align-items:baseline; gap:16px;
  padding:11px 0; border-bottom:1px solid var(--border); list-style:none;
}
.cierre-lista > li:last-child { border-bottom:0; }
.cierre-lista .cl-que { color:var(--text); }
.cierre-lista .cl-cifra { font-variant-numeric:tabular-nums; font-weight:650; color:var(--navy); white-space:nowrap; }
.cierre-lista .cl-nota { display:block; color:var(--muted); font-size:12.5px; margin-top:2px; }
.cierre-lista li[data-tono="ok"] .cl-cifra { color:var(--ok); }

.cierre-acciones { display:flex; flex-wrap:wrap; gap:10px; }
.cierre-acciones .btn { flex:0 0 auto; }

/* La despedida ocupa la ventana entera: después de cerrar no hay panel al
   que volver, así que dejar el dashboard vivo detrás sería enseñar una
   puerta que ya no abre. */
.adios {
  position:fixed; inset:0; z-index:300; display:grid; place-items:center;
  padding:24px; background:var(--bg); text-align:center;
  opacity:0; transition:opacity .4s cubic-bezier(.22,1,.36,1);
}
.adios[data-visible="true"] { opacity:1; }
.adios-caja { max-width:420px; display:grid; gap:12px; justify-items:center; }
/* La marca vive en la barra oscura, donde el texto es blanco. Aquí el fondo
   es claro: se le devuelve el color de siempre en vez de dejarla invisible. */
.adios-mark { margin-bottom:6px; }
.adios-mark .wordmark { color:var(--navy); }
.adios h1 { margin:0; font-size:24px; }
.adios p { margin:0; color:var(--muted); font-size:14px; line-height:1.6; }

@media (prefers-reduced-motion: reduce) {
  .cierre-panel, .adios { transition:none; }
}
