/* ═══════════════════════════════════════════════════════════════════════════
   Xeed Atlas — Capa de refinamiento visual (v5)
   ═══════════════════════════════════════════════════════════════════════════

   QUÉ ES ESTO
   Una capa CSS que se carga DESPUÉS de app.css y de los <style> de cada página.
   No reemplaza nada: corrige y ordena lo que ya existe. No toca HTML ni JS.

   QUÉ ARREGLA (medido sobre el código, no es una impresión)
   · 9 tokens usados pero NO definidos, con 53 usos en total. El peor:
     --xn-text-secondary (31 usos) → hoy esas declaraciones no pintan nada.
   · .xn-btn-sm era idéntico a .xn-btn, así que no había jerarquía de botones.
   · JetBrains Mono se declaraba en el stack y NUNCA se cargaba: los números,
     bytes y el log se veían en Courier.
   · La sidebar tenía --sb-text #94A3B8 sobre #2B344A ≈ 3.9:1, por debajo de AA.
   · 6 clases usadas sin ningún estilo: .xn-table-compact, .failed,
     .js-enable-sync, .js-disable-sync, .btn-oauth, .btn-del.
   · 7 de 10 páginas traían su propio mini design system en <style> (844 líneas):
     .kpi-card, .tab-btn, .login-*, .mj-*, .as-*, .ft-*. Aquí se alinean con el
     sistema central para que todas se vean de la misma familia.
   · Modales que dependían del hack [style*="display: flex"].
   · Sin estilos de foco visibles, sin estados :disabled y sin
     prefers-reduced-motion.

   PARA REVERTIR: borrar el <link> a este fichero en las páginas (o
   `git checkout -- app/public` si quieres volver al commit inicial).
   ═══════════════════════════════════════════════════════════════════════════ */

/* La monoespaciada también va con la aplicación: misma razón que la de texto. */
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 100 800;
  font-display: swap;
  src: url('/assets/fonts/jetbrains-mono-latin.woff2') format('woff2');
}

/* ── 1 · Tokens que faltaban ───────────────────────────────────────────────
   Se definen como ALIAS de la paleta existente, no como colores nuevos:
   así se arreglan los 53 usos rotos sin cambiar el aspecto general.        */
:root {
  --xn-surface:         var(--bg-card);
  --xn-surface-2:       var(--bg-secondary);
  --xn-border:          var(--border);
  --xn-text-primary:    var(--text-primary);
  --xn-text-secondary:  var(--text-secondary);
  --xn-bg-secondary:    var(--bg-secondary);
  --xn-bg-card:         var(--bg-card);
  --xn-bg-alt:          var(--bg-primary);
  --text-muted:         var(--text-secondary);
  --font-mono:          'JetBrains Mono', 'Fira Mono', 'Courier New', monospace;

  /* Ritmo: una sola escala de espacio para todo el producto */
  /* Una sola escala de espacio. Aquí estaba DUPLICADA: --sp-N con los mismos
     valores que --sN en app.css, así que cada página elegía la que quería.
     Los nombres viejos se quedan como alias. */
  --sp-1: var(--s1); --sp-2: var(--s2); --sp-3: var(--s3); --sp-4: var(--s4);
  --sp-5: var(--s5); --sp-6: var(--s6); --sp-7: var(--s7); --sp-8: var(--s8);

  /* ── Lo que el acceso necesitaba y no existía ─────────────────────────────
     El <style> de index.html declaraba estos colores a mano, y un literal es
     inmune al tema. Los que ya tenían equivalente NO se renombran: el fondo del
     panel de marca es --xn-hero-bg (#1e2d42, definido así en app.css) y los
     grises son --text-muted. Estos son los que no tenían token. */
  /* Los dos resplandores de la portada. El panel es oscuro en AMBOS temas
     (--xn-hero-bg), así que su alfa se mide siempre contra el mismo fondo y no
     cambia con el tema: por eso viven solo aquí. */
  --login-glow:      rgba(229, 65, 37, 0.28);
  --login-glow-soft: rgba(229, 65, 37, 0.15);
  /* Tercer escalón del naranja de marca: el degradado del botón de entrada se
     oscurece al pasar el ratón. #A82C14 con texto blanco da 6.92:1 (medido),
     por encima del 5.24:1 del escalón intermedio; el hover oscurece, no aclara.
     Como --xn-primary-solid y -2, es RELLENO con texto blanco encima, así que
     tampoco se redefine en oscuro. */
  --xn-primary-solid-3: #A82C14;

  /* Foco visible y consistente */
  /* --focus-ring se define en app.css, que es el sistema. Aquí estaba
     duplicado con otro valor, y como esta capa carga después, ganaba el de
     aquí: dos verdades para lo mismo. */
}

/* Contraste de la sidebar: el tema claro ya viene bien resuelto en app.css
   (--sb-text #596380 sobre #FFFFFF ≈ 6.5:1). Solo se corrige el oscuro,
   donde #94A3B8 sobre #2B344A se quedaba en ≈ 3.9:1, por debajo de AA. */
/* La sección del sidebar sube a 0.60: con 0.50 sobre #2B344A daba 4.40:1
   (medido con theme-contrast), por debajo de AA para texto de 9.5px. */
[data-theme="dark"] {
  --sb-text:    #B9C2D0;
  --sb-section: rgba(255, 255, 255, 0.60);
}

/* Los tokens que añadió el acceso (--login-glow, --login-glow-soft,
   --xn-primary-solid-3) NO se repiten aquí a propósito: se miden contra
   superficies que no cambian con el tema —la portada es oscura en los dos, y el
   botón primario es un relleno con texto blanco—, y repetir un valor igual en
   dos bloques es la segunda verdad que un día se separa. El motivo de cada uno
   está escrito en :root. */

/* El HTML llega en español. Si el idioma guardado no es el de origen, el
   script del <head> marca <html data-i18n-pending> y aquí se esconde el
   cuerpo hasta que el motor traduce. Quien lee español no paga nada porque el
   atributo no llega a ponerse. */
html[data-i18n-pending] body { visibility: hidden; }

/* ── 2 · Base y tipografía ─────────────────────────────────────────────────
   Tipografía con escala real. Se mantiene la base de 14px (densidad de
   herramienta) pero con jerarquía medible y números tabulares.              */
body { font-feature-settings: 'cv11', 'ss01'; }

h1, .xn-page-title {
  /* Título fluido: los dos extremos salen de la escala; crecer entre ellos es
     la decisión (de --t-xl a --t-2xl según el ancho). */
  font-size: clamp(var(--t-xl), 1.1rem + 0.9vw, var(--t-2xl));
  font-weight: 600;
  letter-spacing: -0.02em;
  line-height: 1.2;
}
h2 { font-size: var(--t-lg); font-weight: 600; letter-spacing: -0.01em; }
h3 { font-size: var(--t-md); font-weight: 600; }

.xn-page-sub { color: var(--text-secondary); font-size: var(--t-body); line-height: 1.5; max-width: 78ch; }

/* Todo lo numérico en mono y alineado: cuentas, bytes, fechas, UIDs */
.xn-stat-value, .xn-progress-pct, .mono, code, pre,
.xn-table td.num, .xn-table td:has(.mono) {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.01em;
}

::selection { background: var(--xn-primary-light2); color: var(--xn-secondary-dark); }

/* Foco visible en todo lo interactivo (hoy no existe) */
a:focus-visible, button:focus-visible, input:focus-visible,
select:focus-visible, textarea:focus-visible, [tabindex]:focus-visible {
  outline: none;
  box-shadow: var(--focus-ring);
  border-radius: var(--radius-sm);
}

/* ── 3 · Sidebar ───────────────────────────────────────────────────────────
   Más legible y con el estado activo inconfundible.

   OJO — regla aprendida a golpes: aquí NO se declara ningún color blanco fijo
   ni borde blanco fijo. app.css ya resuelve ambos con tokens conscientes del
   tema (--sb-border, --sb-text-active, --sb-hover-bg), y esta capa se carga
   después, así que un valor fijo GANA sobre el token y rompe el tema claro. */
.xn-sidebar-nav .xn-nav-item {
  border-radius: var(--radius-sm);
  transition: background 140ms ease, color 140ms ease;
  font-weight: 500;
}
/* El hover no fija color a propósito: lo hereda de app.css
   (.xn-nav-item:hover → var(--sb-text-active)), que es #2B344A en claro
   (11.98:1 sobre #F9FAFB) y #FFFFFF en oscuro. Aquí había un #fff fijo que en
   claro daba 1.03:1 — la etiqueta desaparecía al pasar el ratón. */
.xn-sidebar-nav .xn-nav-item.active,
.xn-sidebar-nav .xn-nav-item[aria-current="page"] {
  background: var(--sb-active-bg);
  color: var(--sb-text-active);
  font-weight: 600;
  position: relative;
}
.xn-sidebar-nav .xn-nav-item.active::before,
.xn-sidebar-nav .xn-nav-item[aria-current="page"]::before {
  content: ''; position: absolute; left: -10px; top: 50%;
  width: 3px; height: 18px; margin-top: calc(var(--s2) * -1);
  background: var(--xn-primary); border-radius: 0 3px 3px 0;
}
/* El activo en tema claro necesita su propio realce (antes: solo oscuro).
   #C9351B y no --xn-primary: el naranja de marca sobre este fondo rosa daba
   3.77:1 (medido) y no llega a AA para texto de 11.6px. Este da 4.81:1. */
html:not([data-theme="dark"]) .xn-sidebar-nav .xn-nav-item.active,
html:not([data-theme="dark"]) .xn-sidebar-nav .xn-nav-item[aria-current="page"] {
  background: var(--xn-primary-light4, #FEF3F0);
  color: #C9351B;
}
.xn-nav-section { color: var(--sb-section); letter-spacing: 0.09em; font-size: var(--t-2xs); font-weight: 700; text-transform: uppercase; }
.xn-nav-badge { font-family: var(--font-mono); font-size: var(--t-2xs); }

/* ── 4 · Topbar y cabeceras de página ──────────────────────────────────── */
.xn-topbar {
  align-items: center;
  padding: var(--s4) var(--s5);
  backdrop-filter: blur(10px);
  background: color-mix(in srgb, var(--bg-secondary) 88%, transparent);
  border: 1px solid var(--border);
  border-radius: 16px;
  box-shadow: var(--shadow-sm);
}
.xn-section-title { font-size: var(--t-xs); font-weight: 700; letter-spacing: 0.07em; text-transform: uppercase; color: var(--text-secondary); }

/* Controles de la casa en la barra superior: tema y idioma. Antes estaban en el
   pie del sidebar, donde no caben. Van separados del resto de acciones con una
   línea, para que no se confundan con los botones de la página. */
.xn-shell-controls {
  display: flex;
  align-items: center;
  gap: var(--s2);
  margin-left: var(--s2);
  padding-left: var(--s3);
  border-left: 1px solid var(--border);
}
.xn-shell-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  padding: 0;
  font-family: inherit;
  color: var(--ink-2);
  background: none;
  border: 1px solid transparent;
  border-radius: var(--r-sm);
  cursor: pointer;
  transition: color 140ms ease, background 140ms ease, border-color 140ms ease;
}
.xn-shell-btn:hover { color: var(--ink); background: var(--bg-primary); border-color: var(--border); }
.xn-shell-btn:focus-visible { outline: none; box-shadow: var(--focus-ring); }
/* El conmutador de idioma flota en las páginas sueltas; dentro de la barra
   ocupa su sitio, como un control más. */
.xn-shell-controls .xn-lang-switch,
.xn-controles-sueltos .xn-lang-switch { position: static; top: auto; right: auto; }

/* ── 5 · Tarjetas y métricas ─────────────────────────────────────────────
   Una sola altura de padding y sombras casi planas: el borde separa,
   la sombra solo insinúa.                                                  */
.xn-card, .xn-stat-card, .kpi-card {
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--bg-card);
  box-shadow: var(--shadow-sm);
  transition: box-shadow 160ms ease, border-color 160ms ease, transform 160ms ease;
}
.xn-card:hover, .kpi-card:hover { box-shadow: var(--shadow-md); }
.xn-card-header, .xn-card-title { padding: var(--sp-4) var(--sp-5); }
.xn-card-body { padding: var(--sp-5); }
.xn-card-footer { padding: var(--sp-4) var(--sp-5); border-top: 1px solid var(--border); }

/* Las KPI del dashboard traían una barra de color de 3px y 5 colores
   distintos. Se atenúa a 2px y se reserva el color para lo semántico:
   el ojo deja de saltar entre KPIs y se concentra en las cifras.          */
/* Tarjetas de cifra. Estaban definidas dentro de la página de migraciones, así
   que el panel no las podía usar sin copiarlas. */
.kpi-grid {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: var(--s4);
  margin-bottom: var(--s5);
}
@media (max-width: 1100px) { .kpi-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (max-width: 640px)  { .kpi-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

.kpi-card {
  display: flex;
  flex-direction: column;
  gap: var(--s1);
  padding: var(--s4);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--e1);
  color: inherit;
  font: inherit;
  text-align: left;
  transition: border-color 140ms ease, box-shadow 140ms ease, background 140ms ease;
}
.kpi-card:hover  { border-color: var(--brand); box-shadow: var(--e2); }
.kpi-card.active { border-color: var(--brand); background: var(--brand-soft); }
.kpi-label { font-size: var(--t-2xs); color: var(--ink-3); text-transform: uppercase; letter-spacing: 0.07em; font-weight: 700; }
.kpi-sub   { font-size: var(--t-xs); color: var(--ink-3); }
.kpi-delta { font-size: var(--t-xs); font-weight: 600; }

.kpi-card::before { height: 2px !important; opacity: 0.85; }
.kpi-card { animation: none; }        /* fadeIn escalonado: ruido al refrescar cada 30 s */
.kpi-value, .xn-stat-value {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  letter-spacing: -0.02em;
  line-height: 1.1;
}
.kpi-label, .xn-stat-label { font-size: var(--t-sm); color: var(--text-secondary); font-weight: 500; }
.kpi-value, .prog-pct, .xn-stat-value {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}

/* La barra de progreso venía en morado (#6366F1 → #818CF8), que no es de la
   marca y además se lee como "otra cosa". El progreso es del color de acento. */
.prog-bar-inner, .xn-progress-bar {
  background: var(--xn-primary);
  background-image: none;
}
.prog-bar-outer { height: 6px; background: var(--border); border-radius: 999px; }
.prog-pct { min-width: 38px; text-align: right; font-weight: 600; }
.xn-stat-icon { border-radius: var(--radius-sm); }
/* Varias cifras seguidas en una fila. Se usa en la cuenta previa del asistente,
   donde lo que importa es comparar origen y destino de un vistazo. */
.xn-stat-rejilla {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: var(--s4);
}

/* Selector de modo: una cuenta o un dominio entero. Estaba dentro de la
   página de grupo, así que la de una cuenta no tenía forma de llegar a la
   otra aunque la otra sí llevaba aquí. Vive en el sistema porque lo usan las
   dos. */
.xn-modo-rejilla {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--s4);
  margin-bottom: var(--s4);
}
@media (max-width: 760px) { .xn-modo-rejilla { grid-template-columns: 1fr; } }
.xn-modo {
  display: block;
  padding: var(--s4);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  background: var(--bg-card);
  color: inherit;
  text-decoration: none;
}
.xn-modo:hover { border-color: var(--xn-secondary-light); }
.xn-modo[aria-current="page"] { border-color: var(--xn-brand-ink); box-shadow: var(--focus-ring); }
.xn-modo b { display: block; margin-bottom: var(--s1); font-size: var(--t-md); }
.xn-modo span { font-size: var(--t-sm); color: var(--text-secondary); }

/* ── Tarjeta de migración ────────────────────────────────────────────────
   Es la pieza central de la lista, y sale del mockup aprobado: la ruta, el
   estado, lo que se ha movido y —lo que se lee primero— EL PORCENTAJE EN
   GRANDE. En una tabla eso estaba en una columna de números pequeños que había
   que comparar fila a fila; aquí se ve de un vistazo cuál va por la mitad.

   Un lote de 248 cuentas es UNA tarjeta: antes eran 248 filas con la misma
   información repetida. */
.xn-proys {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(420px, 1fr));
  gap: var(--s5);
  padding: var(--s5);
}
.charts-grid > .xn-card > .chart-wrap,
.charts-grid > .xn-card > .donut-wrap {
  margin: var(--s4) var(--s5) var(--s5);
}
.charts-grid > .xn-card > .status-legend {
  margin: 0 var(--s5) var(--s5);
}
.xn-card > .lote-barra {
  margin: var(--s4) var(--s5) 0;
}
.xn-proy { padding: var(--s4) var(--s5); }
.xn-proy-fila {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: var(--s5);
  align-items: center;
}
@media (max-width: 560px) { .xn-proy-fila { grid-template-columns: 1fr; } }
.xn-proy-info { display: flex; flex-direction: column; gap: var(--s2); min-width: 0; }
.xn-proy-cabecera { display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap; }
.xn-proy-titulo {
  margin: 0;
  font-size: var(--t-md);
  font-weight: 700;
  color: var(--ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.xn-proy-meta { font-size: var(--t-sm); color: var(--ink-3); }
.xn-proy-cifra { display: flex; flex-direction: column; align-items: flex-end; gap: var(--s1); }
.xn-proy-pct {
  font-family: var(--mono);
  font-size: var(--t-2xl);
  font-weight: 600;
  letter-spacing: -0.03em;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
  line-height: 1;
}
.xn-proy-cuentas { font-size: var(--t-xs); color: var(--ink-3); white-space: nowrap; }
.xn-proy-barra {
  height: 5px;
  border-radius: var(--r-pill);
  background: var(--sunken);
  overflow: hidden;
  margin-top: var(--s1);
}
.xn-proy-barra > i { display: block; height: 100%; border-radius: inherit; background: var(--brand); transition: width 300ms ease; }
.xn-proy-barra.ok > i   { background: var(--xn-success); }
.xn-proy-barra.warn > i { background: var(--xn-warning); }
.xn-proy-barra.bad > i  { background: var(--xn-danger); }

/* ── Cifras grandes y incidencias ────────────────────────────────────────
   Del mockup del detalle: los números que se vienen a ver (correos movidos,
   volumen, tiempo, incidencias) en grande y en fila, en vez de repartidos por
   tarjetas. Y las incidencias AGRUPADAS POR CAUSA: cuarenta fallos por mensaje
   son tres decisiones, y cada una dice qué hacer. */
.xn-metricas { display: flex; gap: var(--s6); flex-wrap: wrap; }
.xn-metrica { display: flex; flex-direction: column; gap: var(--s1); }
.xn-metrica .k {
  font-family: var(--font-mono);
  font-size: var(--t-xl);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.02em;
  color: var(--ink);
}
.xn-metrica .l { font-size: var(--t-sm); color: var(--ink-3); }
.xn-metrica.aviso .k { color: var(--xn-warning-ink); }

.xn-incidencia {
  display: flex;
  align-items: center;
  gap: var(--s4);
  padding: var(--s3) 0;
  border-bottom: 1px solid var(--line);
}
.xn-incidencia:last-child { border-bottom: 0; }
.xn-incidencia .code {
  font-family: var(--font-mono);
  font-size: var(--t-sm);
  color: var(--ink-3);
  min-width: 96px;
}
.xn-incidencia .txt { flex: 1; min-width: 0; }
.xn-incidencia .txt b { display: block; font-size: var(--t-base); font-weight: 600; color: var(--ink); }
.xn-incidencia .txt span { font-size: var(--t-sm); color: var(--ink-2); }
.xn-incidencia .num {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  color: var(--ink);
}

/* ── 6 · Tablas ──────────────────────────────────────────────────────────
   Cabecera fija al hacer scroll, filas cómodas, números alineados a la
   derecha y sin rayas verticales.                                          */
.xn-table-wrap { border: 1px solid var(--border); border-radius: var(--radius-md); overflow: auto; background: var(--bg-card); }
.xn-table { width: 100%; border-collapse: separate; border-spacing: 0; font-size: var(--t-body); }
.xn-table thead th {
  position: sticky; top: 0; z-index: 2;
  background: var(--bg-primary);
  font-size: var(--t-xs); font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--text-secondary);
  padding: var(--s3) var(--sp-4);
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
}
.xn-table tbody td { padding: var(--s3) var(--sp-4); border-bottom: 1px solid var(--border); vertical-align: middle; }
.xn-table tbody tr:last-child td { border-bottom: 0; }
.xn-table tbody tr { transition: background 120ms ease; }
.xn-table tbody tr:hover { background: var(--bg-hover); }
.xn-table td:last-child, .xn-table th:last-child { text-align: right; }

/* Los botones de acciones se apilaban en tres filas (Ver / Detener / Borrar),
   así que cada fila medía 146px y la tabla se volvía incómoda de leer.
   El contenedor trae `flex-wrap: wrap` como estilo EN LÍNEA en el HTML
   generado, y un estilo en línea solo lo gana un !important: es el único
   uso deliberado de !important en esta capa, y está acotado a esa celda. */
.xn-table td:last-child { white-space: nowrap; }
.xn-table td:last-child > div { flex-wrap: nowrap !important; }
.xn-table td:last-child > .xn-btn + .xn-btn { margin-left: var(--s2); }
.xn-table td:last-child .xn-btn-sm,
.xn-table td:last-child .xn-btn-ghost { padding: 0 var(--s2); }
.xn-table td.num { text-align: right; }
.xn-table-compact tbody td { padding: var(--s2) var(--sp-3); }
.xn-table-compact { font-size: var(--t-base); }

/* Estado vacío: antes era una celda gris con "Cargando…" */
.xn-table-empty {
  text-align: center; color: var(--text-secondary);
  padding: var(--sp-8) var(--sp-5) !important;
  font-size: var(--t-body); line-height: 1.7;
}
.xn-table-empty::before {
  content: '—'; display: block; font-size: var(--t-xl); color: var(--border);
  margin-bottom: var(--sp-2); font-family: var(--font-mono);
}

/* ── 7 · Botones ─────────────────────────────────────────────────────────
   Jerarquía real: un primario por zona, secundario para alternativas,
   fantasma para acciones terciarias. Tamaños de 30/36/44 px.              */
.xn-btn {
  height: 36px; padding: 0 var(--sp-4);
  border-radius: var(--radius-sm);
  font-size: var(--t-body); font-weight: 500;
  display: inline-flex; align-items: center; justify-content: center; gap: var(--sp-2);
  border: 1px solid var(--border);
  background: var(--bg-card); color: var(--text-primary);
  cursor: pointer; white-space: nowrap;
  transition: background 140ms ease, border-color 140ms ease, transform 100ms ease;
}
.xn-btn:hover { background: var(--bg-primary); border-color: var(--xn-secondary-light2); }
.xn-btn:active { transform: translateY(1px); }
.xn-btn:disabled, .xn-btn[disabled], .xn-btn.disabled { opacity: 0.5; cursor: not-allowed; transform: none; }

.xn-btn-primary { background: var(--xn-primary-solid); border-color: var(--xn-primary-solid); color: var(--text-on-brand); font-weight: 600; }
.xn-btn-primary:hover { background: var(--xn-primary-solid-2); border-color: var(--xn-primary-solid-2); }
.xn-btn-secondary { background: var(--bg-card); border-color: var(--xn-secondary-light2); color: var(--xn-secondary); font-weight: 500; }
.xn-btn-secondary:hover { background: var(--bg-primary); border-color: var(--xn-secondary); }
.xn-btn-danger { background: var(--xn-danger-solid); border-color: var(--xn-danger-solid); color: var(--text-on-brand); font-weight: 600; }
.xn-btn-danger:hover { background: var(--xn-danger-solid-2); border-color: var(--xn-danger-solid-2); }
.xn-btn-ghost { background: transparent; border-color: transparent; color: var(--text-secondary); }
.xn-btn-ghost:hover { background: var(--bg-primary); color: var(--text-primary); }

/* Antes .xn-btn-sm era idéntico a .xn-btn: no había jerarquía */
.xn-btn-sm { height: 30px; padding: 0 var(--sp-3); font-size: var(--t-sm); border-radius: var(--radius-sm); }
.xn-btn-lg { height: 44px; padding: 0 var(--sp-5); font-size: var(--t-md); }
.xn-btn-sm.xn-btn-primary { font-weight: 600; }
.xn-btn-icon { width: 36px; height: 36px; padding: 0; }
.xn-btn-icon.xn-btn-sm { width: 30px; height: 30px; }
.xn-btn-full { width: 100%; }

/* Clases que existían sin estilo */
.btn-oauth { border-color: var(--xn-info); color: var(--xn-info); }
.btn-oauth:hover { background: var(--xn-info-soft); }
.btn-del { color: var(--xn-danger); }
.btn-del:hover { background: var(--xn-danger-soft); border-color: var(--xn-danger); }
.js-enable-sync, .js-disable-sync { font-size: var(--t-sm); }

/* ── 8 · Formularios ─────────────────────────────────────────────────────
   Etiqueta clara, campo con foco evidente, ayuda legible y error visible.  */
.xn-label, .xn-form-group > label {
  display: block; margin-bottom: var(--s2);
  font-size: var(--t-base); font-weight: 600; color: var(--text-secondary);
}
.xn-input, .xn-select, .xn-textarea {
  width: 100%; min-height: 38px; padding: var(--s2) var(--sp-3);
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--bg-card); color: var(--text-primary);
  font-size: var(--t-body);
  transition: border-color 140ms ease, box-shadow 140ms ease;
}
.xn-input:hover, .xn-select:hover, .xn-textarea:hover { border-color: var(--xn-secondary-light2); }
.xn-input:focus, .xn-select:focus, .xn-textarea:focus {
  outline: none; border-color: var(--xn-brand-ink); box-shadow: var(--focus-ring);
}
.xn-input:disabled, .xn-select:disabled { background: var(--bg-primary); color: var(--text-secondary); cursor: not-allowed; }
.xn-input::placeholder, .xn-textarea::placeholder { color: var(--xn-secondary-light2); }
.xn-input.mono, .xn-input[inputmode="numeric"] { font-family: var(--font-mono); }
.xn-input[aria-invalid="true"], .xn-input.is-invalid { border-color: var(--xn-danger); }

.xn-form-group { margin-bottom: var(--sp-4); }
.xn-label-row { display: flex; align-items: center; gap: 6px; margin-bottom: var(--s2); }
.xn-label-row .xn-label { margin-bottom: 0; }
.xn-info {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  padding: 0;
  flex-shrink: 0;
  color: var(--text-secondary);
  background: transparent;
  border: 0;
  border-radius: 50%;
  cursor: help;
}
.xn-info:hover, .xn-info:focus-visible { color: var(--text-primary); }
.xn-info:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.xn-info-tip {
  position: absolute;
  left: calc(100% + 8px);
  top: 50%;
  transform: translateY(-50%);
  width: max-content;
  max-width: 260px;
  padding: 8px 10px;
  border-radius: 8px;
  background: var(--ink, #1c2430);
  color: #fff;
  font-size: var(--t-xs);
  font-weight: 500;
  line-height: 1.4;
  text-align: left;
  white-space: normal;
  opacity: 0;
  pointer-events: none;
  z-index: 20;
  box-shadow: var(--shadow-md);
}
.xn-info:hover .xn-info-tip,
.xn-info:focus-visible .xn-info-tip { opacity: 1; }
.auth-par {
  display: grid;
  grid-template-columns: 200px 1fr;
  gap: 1rem;
  align-items: start;
}
@media (max-width: 700px) {
  .xn-info-tip {
    left: 0;
    right: auto;
    top: calc(100% + 8px);
    transform: none;
    max-width: min(260px, 70vw);
  }
  .auth-par { grid-template-columns: 1fr; }
}
.xn-form-actions { display: flex; gap: var(--sp-3); align-items: center; flex-wrap: wrap; padding-top: var(--sp-2); }
.xn-field-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: var(--sp-3); }

/* Interruptor: más grande y con etiqueta alineada */
.xn-switch-row { display: flex; align-items: center; gap: var(--sp-4); padding: var(--sp-2) 0; }
.xn-switch-thumb { box-shadow: var(--shadow-sm); }

/* ── 9 · Insignias de estado ─────────────────────────────────────────────
   Mismo tamaño y peso para todas: la diferencia debe ser el color, no la
   forma. Así una tabla con 8 estados distintos se lee de un vistazo.      */
.xn-badge, .xn-status-dot {
  display: inline-flex; align-items: center; gap: var(--s1);
  height: 22px; padding: 0 var(--s2); border-radius: 999px;
  font-size: var(--t-xs); font-weight: 600; letter-spacing: 0.01em;
  white-space: nowrap;
}
.xn-badge-running, .xn-badge-syncing { animation: xnPulse 2.4s ease-in-out infinite; }
@keyframes xnPulse { 50% { opacity: 0.72; } }
.xn-badge-failed, .failed { color: var(--xn-danger); font-weight: 600; }
.xn-badge-done, .xn-badge-success { color: var(--xn-success); }

/* ── 10 · Pestañas, alertas, progreso ─────────────────────────────────── */
.xn-tabs { border-bottom: 1px solid var(--border); }
.xn-tab {
  border: 0; background: transparent;
  padding: var(--s3) var(--sp-4); margin-bottom: -1px;
  font-size: var(--t-body); font-weight: 500; color: var(--text-secondary);
  border-bottom: 2px solid transparent; cursor: pointer;
  transition: color 140ms ease, border-color 140ms ease;
}
.xn-tab:hover { color: var(--text-primary); }
.xn-tab.active, .xn-tab[aria-selected="true"] { color: var(--text-primary); font-weight: 600; border-bottom-color: var(--xn-brand-ink); }
/* Las pestañas de la página de migraciones (inline .tab-btn) se alinean con el sistema */
.tab-btn { font-size: var(--t-base); font-weight: 500; }
.tab-btn.active { font-weight: 600; }

.xn-alert, .xn-alert-info, .xn-alert-success, .xn-alert-warn, .xn-alert-danger, .xn-alert-error {
  border-radius: var(--radius-sm); padding: var(--sp-3) var(--sp-4);
  font-size: var(--t-base); line-height: 1.55; border-left-width: 3px;
}

.xn-progress, .xn-progress-wrap { border-radius: 999px; overflow: hidden; background: var(--bg-primary); }
.xn-progress-bar { height: 100%; border-radius: inherit; transition: width 260ms cubic-bezier(.4,0,.2,1); }
.xn-progress-pct { font-weight: 600; }

/* ── 11 · Modales y avisos ───────────────────────────────────────────────
   Dejan de depender del hack [style*="display: flex"]: si tu JS pone
   display:flex en el atributo style, esto lo estiliza igual.              */
.xn-modal-backdrop, .xn-overlay {
  background: rgba(24, 32, 48, 0.5);
  backdrop-filter: blur(3px);
}
.xn-modal, [class*="xn-modal"][style*="display: flex"] {
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--e3);
  background: var(--bg-card);
}
.xn-modal-header { padding: var(--sp-5) var(--sp-6); border-bottom: 1px solid var(--border); }
.xn-modal-title { font-size: var(--t-md); font-weight: 600; }
.xn-modal-body { padding: var(--sp-6); }
.xn-modal-footer { padding: var(--sp-4) var(--sp-6); border-top: 1px solid var(--border); display: flex; gap: var(--sp-3); justify-content: flex-end; }
.xn-modal-close { border-radius: var(--radius-sm); }
.xn-modal-close:hover { background: var(--bg-primary); }

.xn-toast {
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  box-shadow: var(--e3);
  font-size: var(--t-body);
  animation: xnToastIn 220ms cubic-bezier(.2,.8,.2,1) both;
}
@keyframes xnToastIn { from { opacity: 0; transform: translateY(8px) scale(.98); } }

/* ── 12 · Log técnico y bloques de código ─────────────────────────────── */
.xn-log {
  font-family: var(--font-mono); font-size: var(--t-xs); line-height: 1.65;
  background: #12151C; color: #C6CEDA;
  border-radius: var(--radius-sm); padding: var(--sp-4);
  border: 1px solid rgba(255, 255, 255, 0.06);
}
.xn-log::-webkit-scrollbar { width: 10px; height: 10px; }
.xn-log::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.16); border-radius: 999px; }
.xn-code-inline { font-family: var(--font-mono); font-size: var(--t-base); padding: 2px var(--s2); border-radius: var(--radius-sm); background: var(--bg-primary); border: 1px solid var(--border); }

/* ── 13 · Login ──────────────────────────────────────────────────────────
   La puerta de entrada del producto: más aire, foco claro en el formulario
   y contraste correcto de los textos sobre el panel oscuro.               */
.login-brand-panel, .login-brand { color: var(--hero-text); }
.login-brand p, .login-brand .login-sub { color: var(--hero-text-dim); }
.login-card, .login-form-card {
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  border: 1px solid var(--border);
}
.login-error { border-radius: var(--radius-sm); font-weight: 500; }

/* ── 14 · Detalle de migración (subsistema .mj-*/.as-*/.ft-*/.fr-*) ─────
   Esta página traía su propio tema con tokens inexistentes; ahora los
   tokens existen (bloque 1) y aquí solo se unifica el acabado.            */
.mj-card, .as-card, .ft-card, .fr-card {
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--bg-card);
  box-shadow: var(--shadow-sm);
}
.mj-title, .as-title, .ft-title { font-size: var(--t-body); font-weight: 600; }
.mj-meta, .as-meta { font-size: var(--t-sm); color: var(--text-secondary); }
.mj-kpi, .as-kpi { font-family: var(--font-mono); font-variant-numeric: tabular-nums; font-weight: 600; }

/* ── 15 · Esqueletos, spinners y utilidades ───────────────────────────── */
.xn-skeleton {
  background: linear-gradient(90deg, var(--bg-primary) 25%, var(--xn-skeleton-b) 37%, var(--bg-primary) 63%);
  background-size: 400% 100%;
  animation: xnShimmer 1.4s ease infinite;
  border-radius: var(--radius-sm);
}
@keyframes xnShimmer { 0% { background-position: 100% 0; } 100% { background-position: 0 0; } }
.xn-spinner { border-width: 2px; }
.text-secondary, .xn-text-secondary { color: var(--text-secondary) !important; }
.xn-text-danger { color: var(--xn-danger); }
.truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ── 16 · Asistente por pasos ─────────────────────────────────────────────
   Un formulario con todo a la vez se rellena a ciegas: no se sabe cuánto
   queda ni qué falta. Aquí cada paso tiene su panel y la barra dice en cuál
   estás.

   Todo esto cuelga de .xn-js, la clase que pone el script del <head> antes de
   pintar. Si ese script no llegara a correr no hay clase: se ven los tres pasos
   seguidos y ninguna barra que no lleve a ninguna parte, es decir, se ve el
   formulario de antes. */
.xn-pasos-barra,
.xn-pasos-nav [data-siguiente],
.xn-pasos-nav [data-anterior] { display: none; }
.xn-js .xn-pasos-barra { display: flex; }
.xn-js .xn-pasos-nav [data-siguiente],
.xn-js .xn-pasos-nav [data-anterior] { display: inline-flex; }

.xn-pasos-barra {
  display: flex;
  align-items: center;
  gap: var(--s2);
  flex-wrap: wrap;
  margin-bottom: var(--s5);
  padding-bottom: var(--s4);
  border-bottom: 1px solid var(--line);
}
.xn-paso-punto {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  padding: var(--s1) var(--s2);
  font-family: inherit;
  font-size: var(--t-sm);
  font-weight: 600;
  color: var(--ink-3);
  background: none;
  border: 0;
  border-radius: var(--r-xs);
  cursor: pointer;
  transition: color 140ms ease;
}
.xn-paso-punto:hover { color: var(--ink); }
.xn-paso-punto:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.xn-paso-punto .n {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  border-radius: var(--r-pill);
  background: var(--sunken);
  color: var(--ink-2);
  font-size: var(--t-2xs);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.xn-paso-punto[aria-current="step"] { color: var(--brand-strong); }
/* El rojo de marca claro (#E54125) con texto blanco se queda en 4,11:1: no llega a AA
   a este tamaño. Se usa el mismo sólido que los botones primarios, que ya pasa. */
.xn-paso-punto[aria-current="step"] .n { background: var(--xn-primary-solid); color: var(--text-on-brand); }
.xn-paso-punto.hecho .n { background: var(--xn-success-soft); color: var(--xn-success-ink); }
.xn-paso-punto.hecho { color: var(--ink-2); }
.xn-paso-sep { width: 24px; height: 1px; background: var(--line); }
@media (max-width: 560px) { .xn-paso-sep { display: none; } }

.xn-js .xn-pasos[data-paso-activo] .xn-paso { display: none; }
.xn-js .xn-pasos[data-paso-activo="1"] .xn-paso[data-paso="1"],
.xn-js .xn-pasos[data-paso-activo="2"] .xn-paso[data-paso="2"],
.xn-js .xn-pasos[data-paso-activo="3"] .xn-paso[data-paso="3"] { display: block; }

.xn-pasos-nav {
  display: flex;
  align-items: center;
  gap: var(--s3);
  flex-wrap: wrap;
  margin-top: var(--s5);
  padding-top: var(--s4);
  border-top: 1px solid var(--line);
}
.xn-pasos-nav .xn-pasos-nota {
  margin-left: auto;
  font-size: var(--t-sm);
  color: var(--ink-3);
}

/* Resumen del paso 3: es una ficha de datos, así que las claves y los valores
   van en columna y los números en monoespaciada tabular. */
.xn-resumen { display: grid; gap: 0; }
.xn-resumen-fila {
  display: flex;
  align-items: baseline;
  gap: var(--s3);
  padding: var(--s2) 0;
  font-size: var(--t-sm);
  border-bottom: 1px solid var(--line);
}
.xn-resumen-fila:last-child { border-bottom: 0; }
.xn-resumen-fila .k { color: var(--ink-2); }
.xn-resumen-fila .v {
  margin-left: auto;
  text-align: right;
  font-weight: 600;
  color: var(--ink);
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  word-break: break-word;
}
.xn-resumen-aviso {
  display: flex;
  gap: var(--s2);
  align-items: flex-start;
  margin-top: var(--s4);
  padding: var(--s3) var(--s4);
  border-radius: var(--r-sm);
  font-size: var(--t-sm);
  line-height: 1.55;
  background: var(--xn-warning-soft);
  color: var(--xn-warning-ink);
}

/* ── 17 · Páginas sueltas (sin sidebar) ──────────────────────────────────
   Cambiar contraseña y la invitación no llevan armazón: centran una tarjeta en
   la pantalla y ponen la marca arriba. Ese centrado y la cabecera estaban
   copiados en cada página con nombres distintos. */
.xn-suelto {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: var(--s5);
}
.xn-suelto-caja { width: 100%; max-width: 440px; }
.xn-marca-cabecera {
  display: flex;
  align-items: center;
  gap: var(--s3);
  margin-bottom: var(--s4);
  padding-left: var(--s1);
}
.xn-marca-logo { height: 26px; width: auto; display: block; }
.xn-suelto-pie {
  text-align: center;
  margin-top: var(--s4);
  font-size: var(--t-sm);
  color: var(--ink-3);
}
.xn-suelto-titulo { margin: 0; font-size: var(--t-xl); font-weight: 700; }
.xn-suelto-producto {
  display: inline-flex;
  align-items: center;
  gap: var(--s1);
  font-size: var(--t-sm);
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-3);
}

/* Controles de la casa en las páginas sueltas: el idioma ya flotaba en la
   esquina y el tema no existía, así que quien entra por el acceso se quedaba
   con el tema del sistema y sin forma de cambiarlo. Ahora van juntos, en el
   mismo sitio donde flotaba el idioma y con el mismo aspecto que en la barra
   (.xn-shell-btn + .xn-lang-switch). La caja recoge el `position: fixed` que
   antes llevaba el conmutador; dentro, el conmutador vuelve al flujo (regla de
   la sección 4) para que los dos controles queden alineados. */
.xn-controles-sueltos {
  position: fixed;
  top: var(--s4);
  right: var(--s4);
  z-index: 200;
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
}

/* El atributo hidden tiene que ganarle a las clases que ponen display: un
   .xn-alert lleva display:flex, así que se seguía viendo aunque estuviera
   oculto. Ya estaba parcheado a mano en xn-connections.css para tres tarjetas;
   se arregla de una vez para todo el sistema, que es donde corresponde. */
[hidden] { display: none !important; }

/* ── 18 · Accesibilidad y movimiento ──────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}
/* Scrollbars discretas en escritorio */
@media (pointer: fine) {
  * { scrollbar-width: thin; scrollbar-color: var(--xn-secondary-light2) transparent; }
}

/* Impresión: los informes que se imprimen no deben llevar la interfaz */
@media print {
  .xn-sidebar, .xn-topbar, .xn-toast, .xn-modal-backdrop { display: none !important; }
  .xn-main { margin: 0 !important; }
  body { background: #fff; }
}
