/* ============================================================================
   Valdra · Consola del cliente — sistema visual

   Los tokens de color son los de docs/brand/tokens.css, copiados aquí en lugar
   de importados: ese archivo no se sirve como estático y un @import remoto sería
   justo la dependencia de red que la plataforma promete no tener. Cuando cambie
   la paleta se cambia en los dos sitios; es el precio de poder demostrar la
   consola con el cable desconectado.

   Reglas del sistema, en orden de importancia:

   1. Un acento y uno solo: ámbar. Si hace falta un segundo color para
      distinguir dos cosas, lo que está mal es el diseño y no la paleta.
      Distribución de superficie 70 neutro / 20 canvas / 7 ámbar / 3 estado. El
      degradado de marca aparece solo en el logotipo. Esto lo abre un empleado
      de banco durante ocho horas; una aplicación de IA de consumo lo cansaría
      en veinte minutos.
   2. El ámbar de marca #F59E0B NO va sobre superficie clara: da 2,06:1 y no es
      legible. Sobre claro se usa --accent, que vale #B45309. Esta regla se
      rompe sola en cuanto alguien escribe un color a mano en una vista, así que
      en las vistas no hay colores literales: hay variables.
   3. Prohibido, y es literal: degradados morados o violetas, glassmorphism,
      resplandores, iconos de chispa, cerebro, varita o robot, emoji como
      iconografía funcional, y animaciones que retrasen una acción.
   4. Densidad alta pero legible. Un banco quiere ver la conversación, no aire.
   5. Los efectos son funcionales y cortos: realce de fila, anillo de foco,
      sombra de superficie elevada, 120–160 ms.

   Este archivo es HERMANO de admin/app.css, no compartido. Las clases se llaman
   igual a propósito —quien haya escrito una vista del portal escribe una de la
   consola sin releer nada— pero evolucionan por separado.
   ============================================================================ */

:root {
  color-scheme: light dark;

  /* ── Marca ─────────────────────────────────────────────────────────────────
     El ámbar de marca y el ámbar utilizable son DOS variables distintas, y esa
     separación no es un capricho de nomenclatura: medido sobre --bg, #F59E0B da
     2,06:1. No es legible, no lo es «casi», y no hay tamaño de letra que lo
     arregle. Sobre el canvas oscuro el mismo ámbar da 9,37:1.

     Consecuencia: --brand-amber solo lo lleva el núcleo del isotipo, que es
     idéntico en los dos temas porque el manual prohíbe recolorear la marca.
     Todo lo demás —texto, borde activo, enlace, icono— usa --accent, que baja
     a ámbar profundo en claro y sube al ámbar de marca en oscuro. */
  --brand-amber: #F59E0B;
  --accent:       #B45309;   /* 4,81:1 sobre --bg */
  --accent-hover: #92400E;
  --accent-deep:  #78350F;
  --accent-soft:  #FCD34D;
  --ink:          #111827;
  --canvas:       #030712;
  --grad: linear-gradient(135deg,#FCD34D 0%,#F59E0B 40%,#B45309 72%,#78350F 100%);

  /* ── Neutros ───────────────────────────────────────────────────────────── */
  --bg:            #F9FAFB;
  --surface:       #FFFFFF;
  --surface-2:     #F3F4F6;
  --surface-3:     #E5E7EB;
  --border:        #E5E7EB;
  --border-strong: #D1D5DB;
  --text:          #111827;
  --text-2:        #4B5563;
  --text-muted:    #6B7280;
  --disabled:      #D1D5DB;

  /* ── Semántica ─────────────────────────────────────────────────────────────
     El manual de marca no tiene paleta de estado: éxito, aviso y error se
     resuelven en ámbar y neutros. Aquí sí la hay, y es una desviación asumida:
     esto no es la web de la marca, es la pantalla donde alguien distingue un
     nodo caído de un lote pendiente durante ocho horas seguidas. El color va
     siempre acompañado de texto, nunca solo. */
  --success: #15803D; --success-bg: #F0FDF4;
  --warning: #B45309; --warning-bg: #FFFBEB;
  --error:   #B91C1C; --error-bg:   #FEF2F2;
  --info:    #4B5563; --info-bg:    #F3F4F6;

  --class-confidential: #A16207;

  /* Fondo de la mitad de marca de la puerta de acceso. Un blanco con un punto
     de calidez, medio grado por delante de --surface: separa el panel del
     formulario sin necesidad de meter una línea más. */
  --gate-aside-bg: #FDFCFB;

  /* ── Barra lateral ─────────────────────────────────────────────────────────
     Canvas casi negro en los dos temas: es la superficie nativa de la marca y
     la constante que hace reconocible la aplicación al cambiar de tema. Sobre
     ella el ámbar de marca sí cumple, así que --rail-accent es el puro. */
  --rail-bg:      #030712;
  --rail-line:    rgba(255,255,255,.08);
  --rail-text:    #9CA3AF;
  --rail-text-hi: #F9FAFB;
  --rail-muted:   #6B7280;
  --rail-hover:   rgba(255,255,255,.06);
  --rail-active:  rgba(245,158,11,.14);
  --rail-accent:  #F59E0B;

  /* ── Tipografía ────────────────────────────────────────────────────────── */
  /* Inter y JetBrains Mono llegan de /assets/brand.css, empaquetadas con el
     producto. La pila del sistema queda detrás como red: si la cara no carga,
     la interfaz sigue leyéndose en vez de quedarse en blanco. El manual fija
     Inter para todo lo que se lee y el mono SOLO para terminal, eyebrows y
     captions numéricos: nunca cuerpo, botones, navegación ni pie. */
  --sans: "Inter", system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
  --mono: "JetBrains Mono", ui-monospace,"SF Mono",SFMono-Regular,Menlo,Consolas,monospace;

  /* ── Escala de espaciado de 4 px ───────────────────────────────────────── */
  --s1: 4px;  --s2: 8px;  --s3: 12px; --s4: 16px;
  --s5: 20px; --s6: 24px; --s7: 32px; --s8: 40px; --s9: 56px;

  /* ── Radios ────────────────────────────────────────────────────────────── */
  --r1: 4px; --r2: 6px; --r3: 8px; --r4: 10px; --r5: 12px;

  /* ── Elevación: dos niveles y ninguno de color ─────────────────────────── */
  --shadow:    0 1px 2px rgba(3,7,18,.05);
  --shadow-2:  0 1px 2px rgba(3,7,18,.06), 0 8px 24px -16px rgba(3,7,18,.24);
  --shadow-lg: 0 12px 40px -14px rgba(3,7,18,.30);

  /* ── Movimiento ────────────────────────────────────────────────────────── */
  --mo: 140ms cubic-bezier(.2,.6,.3,1);
  --mo-fast: 120ms cubic-bezier(.2,.6,.3,1);

  --ring: 0 0 0 3px color-mix(in srgb, var(--accent) 22%, transparent);
  --canvas-max: 1600px;
  --rail-w: 236px;
  --top-h: 48px;
}

/* ── Tema oscuro ─────────────────────────────────────────────────────────────
   Aquí el tema oscuro NO es una inversión del claro: es el original. El canvas
   casi negro con el acento ámbar es la superficie que define la marca, y el
   tema claro es la traducción para quien trabaja en una sucursal con la
   persiana subida.

   Va escrito dos veces a propósito. Una bajo prefers-color-scheme, para quien
   no ha elegido nada; otra bajo [data-theme="dark"], para quien sí. Definir el
   valor una sola vez dentro de la media query dejaría el conmutador manual sin
   nada que sobreescribir al pedir oscuro desde un sistema en claro, y esa es la
   mitad de los casos.                                                          */

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg:            #030712;
    --surface:       #0B1220;
    --surface-2:     #111A2B;
    --surface-3:     #1B2436;
    --border:        rgba(255,255,255,.08);
    --border-strong: rgba(255,255,255,.16);
    --text:          #F9FAFB;
    --text-2:        #D1D5DB;
    --text-muted:    #9CA3AF;
    --disabled:      #4B5563;

    /* Sobre el canvas el ámbar de marca da 9,37:1 y ya no hace falta rebajarlo:
       este es el único tema donde el acento de la interfaz y el de la marca son
       exactamente el mismo color. */
    --accent:       #F59E0B;
    --accent-hover: #FCD34D;
    --accent-deep:  #B45309;

    --success: #4ADE80; --success-bg: rgba(74,222,128,.12);
    --warning: #FBBF24; --warning-bg: rgba(251,191,36,.12);
    --error:   #F87171; --error-bg:   rgba(248,113,113,.12);
    --info:    #9CA3AF; --info-bg:    rgba(255,255,255,.06);

    --class-confidential: #FCD34D;
    --gate-aside-bg: #0A101E;

    /* La barra lateral comparte color con el lienzo, así que se separa por una
       línea y no por un tono: sobre fondo oscuro una sombra no se ve y sí
       ensucia el borde. */
    --rail-bg:   #030712;
    --rail-line: rgba(255,255,255,.10);

    --shadow:    0 1px 2px rgba(0,0,0,.40);
    --shadow-2:  0 1px 2px rgba(0,0,0,.40), 0 8px 26px -18px rgba(0,0,0,.70);
    --shadow-lg: 0 14px 46px -16px rgba(0,0,0,.72);
  }
}

:root[data-theme="dark"] {
  --bg:            #030712;
  --surface:       #0B1220;
  --surface-2:     #111A2B;
  --surface-3:     #1B2436;
  --border:        rgba(255,255,255,.08);
  --border-strong: rgba(255,255,255,.16);
  --text:          #F9FAFB;
  --text-2:        #D1D5DB;
  --text-muted:    #9CA3AF;
  --disabled:      #4B5563;

  --accent:       #F59E0B;
  --accent-hover: #FCD34D;
  --accent-deep:  #B45309;

  --success: #4ADE80; --success-bg: rgba(74,222,128,.12);
  --warning: #FBBF24; --warning-bg: rgba(251,191,36,.12);
  --error:   #F87171; --error-bg:   rgba(248,113,113,.12);
  --info:    #9CA3AF; --info-bg:    rgba(255,255,255,.06);

  --class-confidential: #FCD34D;
  --gate-aside-bg: #0A101E;

  --rail-bg:   #030712;
  --rail-line: rgba(255,255,255,.10);

  --shadow:    0 1px 2px rgba(0,0,0,.40);
  --shadow-2:  0 1px 2px rgba(0,0,0,.40), 0 8px 26px -18px rgba(0,0,0,.70);
  --shadow-lg: 0 14px 46px -16px rgba(0,0,0,.72);
}

/* ── Base ──────────────────────────────────────────────────────────────────── */

* { box-sizing: border-box; }
html, body { height: 100%; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--sans);
  font-size: 13.5px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  font-variant-numeric: tabular-nums;
}

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--r1);
}
[hidden] { display: none !important; }

h1, h2, h3 { margin: 0; }
h1 { font-size: 22px; font-weight: 650; letter-spacing: -.022em; line-height: 1.25; }
h2 { font-size: 14px; font-weight: 620; letter-spacing: -.008em; }
h3 { font-size: 13px; font-weight: 620; }

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; text-underline-offset: 2px; }

::selection { background: color-mix(in srgb, var(--accent) 24%, transparent); }

/* Una sola declaración de movimiento reducido, al final de la base: quien pide
   no ver animaciones no quiere excepciones repartidas por la hoja. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
    scroll-behavior: auto !important;
  }
}

.skip {
  position: absolute; left: -9999px; top: 0; z-index: 100;
  background: var(--surface); color: var(--text);
  padding: var(--s2) var(--s4);
  border: 1px solid var(--border); border-radius: 0 0 var(--r3) 0;
}
.skip:focus { left: 0; }

/* ── Utilidades ────────────────────────────────────────────────────────────── */

.mono { font-family: var(--mono); font-size: .94em; }
.xs   { font-size: 11.5px; }
.t2   { color: var(--text-2); }
.t3   { color: var(--text-muted); }
.right  { text-align: right; }
.center { text-align: center; }
.spacer { margin-left: auto; }
.block  { width: 100%; }
.num {
  font-family: var(--mono); font-variant-numeric: tabular-nums;
  text-align: right; white-space: nowrap; font-size: .95em;
}
.stack   { display: flex; flex-direction: column; gap: var(--s3); }
.rowline { display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap; }
.sr {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap;
}

/* ── Marca ─────────────────────────────────────────────────────────────────── */

.brand { display: flex; align-items: center; gap: var(--s3); min-width: 0; }

/* El isotipo va en SVG y no en PNG por dos razones, y la segunda es la que manda:
   a 28 px un raster se ve blando, y el perímetro tiene que cambiar de color con
   el tema. El SVG hereda currentColor en el trazo y deja el núcleo en ámbar de
   marca, que es lo que el manual exige que no cambie nunca.

   El hexágono es más alto que ancho (razón 0,83), así que la caja lo es también:
   forzarlo a un cuadrado lo dejaría flotando en un carril de aire a los lados. */
.mark {
  width: 26px; height: 31px; flex-shrink: 0;
  color: var(--rail-text-hi);
  font-size: 0;
}
.mark svg { display: block; width: 100%; height: 100%; }

.wm {
  font-size: 16px; font-weight: 700; letter-spacing: -.02em; line-height: 1.1;
  color: var(--rail-text-hi); white-space: nowrap;
}
/* Wordmark en una sola tinta. El manual es explícito: los modelos se nombran en
   tipografía, no se señalan recoloreando la marca; partir «Valdra» en dos
   colores sería justo eso. */
.tag {
  font-size: 10px; letter-spacing: .09em; text-transform: uppercase;
  color: var(--rail-muted); margin-top: 2px; white-space: nowrap;
}
.brand-lg .mark { width: 32px; height: 38px; color: var(--text); }
.brand-lg .wm   { font-size: 20px; color: var(--text); }
.brand-lg .tag  { color: var(--text-muted); font-size: 11px; }

/* ── Puerta de acceso ──────────────────────────────────────────────────────────
   Una lámina apoyada sobre el fondo, y dentro dos mitades: identidad a la
   izquierda, formulario a la derecha. El patrón es el de una consola corporativa
   y no el de una aplicación de consumo, y esa diferencia es deliberada: quien
   entra aquí administra dinero ajeno.

   Las dos mitades siguen al tema. La versión anterior dejaba el panel de marca
   en canvas oscuro siempre; se cambió porque en tema claro el resultado era una
   pantalla partida por la mitad en dos temas distintos, y porque el manual
   permite ámbar profundo sobre superficie clara justamente para esto.

   Lo que NO hay en esta pantalla, y es una decisión y no un olvido: no hay
   «¿olvidaste tu contraseña?» porque la plataforma no tiene recuperación por
   autoservicio —el alta y la reposición las hace el propietario de la cuenta de
   la organización—, y no hay botones de SSO ni de Microsoft porque no hay
   proveedor de identidad federada conectado. Un botón que no lleva a ninguna
   parte en una pantalla de acceso no es un adorno: es una llamada al soporte.  */

.gate {
  position: fixed; inset: 0; overflow: auto;
  background: var(--bg);
  display: grid; place-items: center;
  padding: var(--s6);
}

/* ── Reanudación de sesión ──────────────────────────────────────────────────
   theme.js marca el raíz con data-session antes de que exista esta hoja cuando
   hay sesión guardada. Mientras dure la marca, la puerta no se pinta —ni un
   fotograma— y en su lugar se enseña la marca en espera: no es un cargador con
   porcentaje inventado, es la señal de que la consola vuelve. app.js retira la
   marca al entrar o al devolver a la puerta. !important porque `hidden` del
   navegador y `display:grid` de arriba empatan en especificidad y este estado
   tiene que ganar a los dos. */
html[data-session] .gate { display: none !important; }
.boot {
  position: fixed; inset: 0; z-index: 5;
  display: none; place-items: center;
  background: var(--bg); color: var(--text-muted);
}
html[data-session] .boot { display: grid; }
.boot-mark { width: 44px; height: 52px; color: var(--accent); }
.boot-mark svg { width: 100%; height: 100%; display: block; }
.boot-inner { display: grid; justify-items: center; gap: var(--s3); }
.boot-word { font: 500 12px/1 var(--mono); letter-spacing: .15em; text-transform: uppercase; }
@media (prefers-reduced-motion: no-preference) {
  .boot-mark { animation: boot-breathe 3s ease-in-out infinite; }
  @keyframes boot-breathe { 0%, 100% { opacity: .55; } 50% { opacity: 1; } }
}

/* La lámina. Se le pone un ancho máximo y no se deja crecer: un formulario de
   acceso a 2560 px de ancho separa tanto la etiqueta del campo que deja de
   leerse como una pareja. */
.gate-shell {
  width: 100%; max-width: 76rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r5);
  box-shadow: var(--shadow-lg);
  overflow: hidden;
}
.gate-split {
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
}

/* ── Mitad de marca ───────────────────────────────────────────────────────── */

.gate-aside {
  padding: var(--s8) var(--s7);
  display: flex; flex-direction: column; gap: var(--s5);
  position: relative; overflow: hidden;
  border-right: 1px solid var(--border);
  background: var(--gate-aside-bg, #FDFCFB);
}
/* Dos manchas de ámbar muy diluido. El manual admite «vidrio sobre una mancha»
   como recurso de profundidad y prohíbe el resplandor; la diferencia práctica es
   que la mancha se queda quieta detrás del contenido y el resplandor persigue a
   la marca. Ninguna toca el logotipo. */
.gate-aside::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background:
    radial-gradient(42rem 24rem at 88% 6%, rgba(245,158,11,.10), transparent 62%),
    radial-gradient(34rem 22rem at 4% 96%, rgba(180,83,9,.07), transparent 64%);
}
.gate-aside > * { position: relative; z-index: 1; }

/* flex: none es obligatorio, no cosmético: el panel es una columna flex y sin él
   el navegador encoge la caja del logotipo hasta cero para hacer sitio al resto.
   El síntoma es un panel correcto y sin marca, que es peor que un panel roto
   porque parece intencionado.

   La razón de aspecto sale del archivo real (640×207). Dársela redonda dejaría
   el logotipo nadando en una caja vacía, y ese aire de más se leería como parte
   del espacio de respeto de la marca, que es otra cosa y mide otra cosa. */
.gate-logo {
  flex: none;
  width: 11.5rem; aspect-ratio: 640 / 207;
  background: url("/assets/logo.png") left center / contain no-repeat;
}
/* En oscuro el logotipo cambia de archivo y no de filtro. Un invert() sobre el
   original convertiría el ámbar en azul, que es exactamente el color del que la
   marca huye. */
:root[data-theme="dark"] .gate-logo { background-image: url("/assets/logo-dark.png"); }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .gate-logo { background-image: url("/assets/logo-dark.png"); }
}

/* Filete corto sobre el titular: la misma pieza que el logotipo lleva a los
   lados del descriptor. Marca dónde empieza el mensaje. */
.gate-rule {
  width: 2.25rem; height: 3px; border-radius: 2px;
  background: var(--accent); flex: none;
}

/* El descriptor de marca, y en inglés a propósito: «Sovereign Private AI» es el
   descriptor, no el mensaje. El mensaje —«Tu modelo. Tus muros.»— sí se traduce,
   y vive debajo, en el diccionario. Traducir el descriptor lo convertiría en una
   frase distinta en cada mercado, que es lo contrario de un descriptor. */
.gate-claim {
  margin: 0; max-width: 11ch;
  font-size: clamp(1.9rem, 3vw, 2.55rem); line-height: 1.06;
  letter-spacing: -.035em; font-weight: 800; color: var(--text);
  text-wrap: balance;
}
.gate-claim em {
  display: block; font-style: normal; color: var(--accent);
}
.gate-sell {
  margin: 0; max-width: 36ch; font-size: .97rem; line-height: 1.55;
  color: var(--text-2); text-wrap: pretty;
}

/* La ilustración carga el peso visual del panel y por eso se le deja consumir el
   espacio sobrante en lugar de fijarle una altura: entre 900 px y 1400 px de
   ventana el panel crece bastante, y un dibujo de altura fija dejaría un hueco
   creciente encima de las garantías. */
.gate-art {
  flex: 1 1 auto; min-height: 8rem;
  display: grid; place-items: center;
  margin: var(--s2) 0;
}
.gate-art img { width: 100%; max-width: 24rem; height: 100%; object-fit: contain; }

/* Las tres garantías, en fila. Son el resumen de lo que se promete y por eso van
   pegadas al pie del panel: es lo último que se lee antes de mirar al
   formulario. */
.gate-points {
  list-style: none; margin: 0; padding: var(--s5) 0 0; border-top: 1px solid var(--border);
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--s4);
}
.gate-points li { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.gate-points .pt-ico {
  width: 30px; height: 30px; border-radius: 50%; display: grid; place-items: center;
  border: 1px solid var(--border); background: var(--surface);
  color: var(--accent); flex: none;
}
.gate-points .pt-ico svg { width: 15px; height: 15px; }
.gate-points strong { font-size: .82rem; font-weight: 620; color: var(--text); }
.gate-points span { font-size: .78rem; line-height: 1.4; color: var(--text-muted); text-wrap: pretty; }

.gate-legal {
  margin: 0; font-family: var(--mono); font-size: .68rem; letter-spacing: .12em;
  text-transform: uppercase; color: var(--text-muted);
}

/* ── Mitad de formulario ──────────────────────────────────────────────────── */

.gate-main {
  display: flex; flex-direction: column;
  padding: var(--s6) var(--s7) var(--s7);
  background: var(--bg);
}
/* Los mandos de idioma y tema, arriba y a la derecha, antes del formulario. Van
   aquí y no debajo por orden de uso: quien abre esto de noche o no lee español
   necesita arreglar eso ANTES de escribir su contraseña, no después de fallar al
   escribirla. */
.gate-bar {
  display: flex; justify-content: flex-end; gap: var(--s2);
  margin-bottom: auto; flex-wrap: wrap;
}

/* La tarjeta lleva superficie y borde propios. Sin ellos los campos flotaban
   directamente sobre el panel, y el formulario se leía como un montón de cajas
   sueltas en vez de como una unidad: no había nada que dijera dónde empieza y
   dónde acaba lo que hay que rellenar. */
.gate-card {
  width: 100%; max-width: 24rem; margin: var(--s6) auto auto;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r5);
  box-shadow: var(--shadow-2);
  padding: var(--s7) var(--s6) var(--s6);
  display: flex; flex-direction: column; gap: var(--s4);
}

/* El candado. Es el único icono decorativo de la pantalla y se gana el sitio:
   dice de qué va esto antes de que nadie lea el titular. aria-hidden en el
   marcado — para un lector de pantalla, «candado» delante de «Entrar a tu
   consola» no añade nada. */
.gate-badge {
  width: 46px; height: 46px; border-radius: 50%; margin: 0 auto;
  display: grid; place-items: center;
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  color: var(--accent);
}
.gate-badge svg { width: 20px; height: 20px; }

.gate-card h1 {
  margin: 0; font-size: 1.5rem; letter-spacing: -.028em; font-weight: 700;
  text-wrap: balance; color: var(--text); text-align: center;
}
.gate-sub {
  margin: 0; color: var(--text-2); font-size: .92rem; text-align: center;
  text-wrap: pretty;
}
.gate-form { display: flex; flex-direction: column; gap: var(--s4); }
.gate-form .field { margin-bottom: 0; }

/* Campo con icono a la izquierda y, en la contraseña, botón a la derecha. El
   relleno lateral del input se amplía en la misma medida que ocupa el adorno:
   si se superpusiera sin reservar sitio, el texto pasaría por debajo del icono
   en cuanto alguien escribiera un correo largo. */
.gate-field { position: relative; display: flex; align-items: center; }
.gate-field > svg {
  position: absolute; left: 11px; width: 16px; height: 16px;
  color: var(--text-muted); pointer-events: none;
}
.gate-field input { padding: 9px 11px 9px 34px; }
.gate-field.has-toggle input { padding-right: 38px; }
.gate-field:focus-within > svg { color: var(--accent); }

/* Mostrar la contraseña. Existe porque la alternativa real no es escribirla bien
   a ciegas: es escribirla en el campo de correo para verla, y eso la manda al
   servidor en claro dentro del usuario y la deja en el historial del navegador. */
.gate-reveal {
  position: absolute; right: 6px;
  display: grid; place-items: center; width: 28px; height: 28px;
  border: 0; border-radius: var(--r2); background: none; cursor: pointer;
  color: var(--text-muted);
  transition: color var(--mo-fast), background var(--mo-fast);
}
.gate-reveal:hover { color: var(--text); background: var(--surface-2); }
.gate-reveal svg { width: 16px; height: 16px; }

.gate-submit { padding: 10px var(--s4); font-size: 14px; }
.gate-submit svg { width: 16px; height: 16px; }

/* Sin tono es un error —así lo llama app.js cuando el acceso falla— y con .info
   es una explicación. El borde lateral hace el trabajo que el color por sí solo
   no puede hacer para quien no distingue rojo de gris. */
.gate-msg {
  margin: 0; padding: var(--s3) var(--s4);
  border-radius: var(--r2); border-left: 3px solid var(--error);
  background: var(--error-bg); color: var(--error);
  font-size: .9rem; text-wrap: pretty;
}
.gate-msg.info {
  border-left-color: var(--border-strong);
  background: var(--surface-2); color: var(--text-2);
}
.gate-foot {
  margin: 0; font-size: .85rem; color: var(--text-muted);
  text-align: center; text-wrap: pretty;
}

/* Un <button> que se lee como enlace. Es un botón y no un <a> porque no navega
   a ninguna parte: cambia de formulario dentro de la misma pantalla, y un enlace
   sin destino es una promesa que el teclado no cumple. La clase existía en el
   marcado de la puerta desde el principio pero no estaba definida en ninguna
   hoja, así que estos botones salían con el estilo nativo del navegador — un
   rectángulo gris en mitad de un párrafo. */
.linkish {
  border: 0; background: none; padding: 0;
  font: inherit; color: var(--accent); font-weight: 550;
  cursor: pointer; text-decoration: underline; text-underline-offset: 2px;
  text-decoration-thickness: 1px;
}
.linkish:hover { color: var(--accent-hover); }

/* ── Pie de la lámina ─────────────────────────────────────────────────────── */

.gate-footbar {
  display: flex; align-items: center; justify-content: space-between; gap: var(--s4);
  padding: var(--s4) var(--s7);
  border-top: 1px solid var(--border);
  background: var(--surface-2);
  font-size: .78rem; color: var(--text-muted);
}
.gate-footbar p { margin: 0; }
.gate-footbar .mark { width: 18px; height: 21px; color: var(--accent); }
/* Aviso de «hace falta JavaScript». Reutiliza .gate y .gate-card pero sin la
   lámina de dos mitades: no hay formulario que enmarcar, solo una frase que
   explica por qué la pantalla está vacía. El margen automático de la tarjeta,
   que en la puerta la empuja hacia arriba, aquí la descentraría. */
noscript .gate-card { margin: 0 auto; max-width: 30rem; text-align: center; }

/* ── Adaptación ───────────────────────────────────────────────────────────── */

/* Por debajo de 62rem las dos mitades se apilan: mantenerlas en columnas dejaría
   el formulario en una franja donde no cabe escribir. Lo que sobrevive es el
   logotipo y el mensaje; la ilustración y las garantías se retiran, porque en un
   móvil son un obstáculo entre el usuario y el formulario. */
@media (max-width: 62rem) {
  .gate { padding: 0; place-items: stretch; }
  .gate-shell { max-width: none; border: 0; border-radius: 0; box-shadow: none; min-height: 100%; }
  .gate-split { grid-template-columns: 1fr; }
  .gate-aside {
    padding: var(--s5) var(--s5) var(--s6);
    gap: var(--s4);
    border-right: 0; border-bottom: 1px solid var(--border);
  }
  .gate-art, .gate-points, .gate-legal { display: none; }
  .gate-claim { font-size: 1.75rem; max-width: none; }
  .gate-claim em { display: inline; }
  .gate-sell { display: none; }
  .gate-logo { width: 8.5rem; }
  .gate-main { padding: var(--s5) var(--s4) var(--s7); background: var(--surface); }
  .gate-card {
    margin-top: var(--s4); padding: 0;
    background: none; border: 0; box-shadow: none;
  }
  .gate-footbar { padding: var(--s4) var(--s5); }
}

/* ── Armazón ───────────────────────────────────────────────────────────────── */

.app { display: grid; grid-template-columns: var(--rail-w) minmax(0, 1fr); min-height: 100vh; }
@media (max-width: 960px) { .app { grid-template-columns: 1fr; } }

.rail {
  background: var(--rail-bg);
  border-right: 1px solid var(--rail-line);
  display: flex; flex-direction: column;
  padding: var(--s4) var(--s3) var(--s3);
  gap: var(--s4);
  position: sticky; top: 0; height: 100vh; min-height: 0;
}
@media (max-width: 960px) { .rail { position: static; height: auto; } }

/* ── Navegación ────────────────────────────────────────────────────────────── */

/* Tres grupos rotulados y no una lista de ocho. Mientras fueron cinco entradas
   la lista era lo correcto —cinco cosas se leen de un vistazo—, pero al entrar
   Flujos, Aprobaciones e Integraciones la lista dejó de tener forma. Los grupos
   nombran el oficio, no el módulo del backend: lo que se opera a diario, lo que
   se conecta con el banco y lo que se administra. */
.nav {
  display: flex; flex-direction: column; gap: var(--s4);
  overflow-y: auto; min-height: 0; flex: 1;
  scrollbar-width: thin;
  scrollbar-color: rgba(255,255,255,.16) transparent;
}
.nav::-webkit-scrollbar { width: 6px; }
.nav::-webkit-scrollbar-thumb { background: rgba(255,255,255,.16); border-radius: 3px; }

.nav-group { display: flex; flex-direction: column; gap: 1px; }
/* Los grupos se separan con una línea y no con más aire: en una barra estrecha
   el aire solo se lee como hueco, y la línea sí dice «aquí empieza otra cosa». */
.nav-group + .nav-group { border-top: 1px solid var(--rail-line); padding-top: var(--s4); }
.nav-title {
  font-size: 10.5px; font-weight: 650; letter-spacing: .10em; text-transform: uppercase;
  color: var(--rail-muted); padding: 0 var(--s2); margin: 0 0 var(--s2);
}

.nav button {
  position: relative; display: flex; align-items: center; gap: var(--s2);
  width: 100%; padding: 7px var(--s2); border: 0; border-radius: var(--r2);
  background: none; color: var(--rail-text); font: inherit; font-size: 13px;
  cursor: pointer; text-align: left;
  transition: background var(--mo-fast), color var(--mo-fast);
}
.nav button:hover { background: var(--rail-hover); color: var(--rail-text-hi); }
.nav button[aria-current="page"] {
  background: var(--rail-active); color: var(--rail-text-hi); font-weight: 600;
}
.nav button[aria-current="page"]::before {
  content: ""; position: absolute; left: 0; top: 6px; bottom: 6px;
  width: 2px; border-radius: 0 2px 2px 0; background: var(--rail-accent);
}
.nav .ico { width: 16px; height: 16px; flex-shrink: 0; opacity: .72; transition: opacity var(--mo-fast); }
.nav button:hover .ico,
.nav button[aria-current="page"] .ico { opacity: 1; }
.nav button[aria-current="page"] .ico { color: var(--rail-accent); }
.nav .label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Punto de aviso: la ruta existe en el menú pero su script no se registró. */
.nav .dot {
  width: 5px; height: 5px; border-radius: 50%; background: var(--warning);
  margin-left: auto; flex-shrink: 0;
}

/* ── Pie de la barra lateral ───────────────────────────────────────────────── */

.rail-foot {
  display: flex; flex-direction: column; gap: var(--s2);
  border-top: 1px solid var(--rail-line); padding-top: var(--s3);
}
.who { font-size: 11.5px; color: var(--rail-muted); line-height: 1.45; min-width: 0; }
.who b {
  color: var(--rail-text-hi); font-weight: 600; font-size: 12.5px;
  display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.who .mail, .who .org {
  display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.who .org { color: var(--rail-text); margin-top: 2px; }
.who .role {
  display: inline-block; margin-top: var(--s1); padding: 1px 6px;
  border-radius: var(--r1); border: 1px solid color-mix(in srgb, var(--accent-soft) 55%, transparent);
  color: var(--accent-soft); font-size: 10.5px; font-weight: 600;
  font-family: var(--mono); letter-spacing: -.01em;
}

/* Selectores de la barra lateral: entorno e idioma. Nombres, nunca banderas —
   una bandera es un país y no una lengua, y en portugués de Brasil se nota. */
.picker { position: relative; display: block; }
.picker > svg {
  position: absolute; left: 8px; top: 50%; transform: translateY(-50%);
  width: 14px; height: 14px; color: var(--rail-muted); pointer-events: none;
}
.picker::after {
  content: ""; position: absolute; right: 10px; top: 50%;
  width: 6px; height: 6px; margin-top: -4px; pointer-events: none;
  border-right: 1.5px solid var(--rail-muted); border-bottom: 1.5px solid var(--rail-muted);
  transform: rotate(45deg);
}
.picker select {
  appearance: none; -webkit-appearance: none;
  width: 100%; padding: 6px 24px 6px 27px;
  background: rgba(255,255,255,.05); border: 1px solid var(--rail-line);
  border-radius: var(--r2); color: var(--rail-text);
  font-family: var(--sans); font-size: 12.5px; cursor: pointer;
  transition: background var(--mo-fast), border-color var(--mo-fast);
}
.picker select:hover { background: rgba(255,255,255,.09); color: var(--rail-text-hi); }
.picker select option { background: var(--rail-bg); color: var(--rail-text-hi); }

.rail-foot .btn.ghost {
  background: rgba(255,255,255,.05); border-color: var(--rail-line); color: var(--rail-text);
}
.rail-foot .btn.ghost:hover {
  background: rgba(255,255,255,.10); border-color: rgba(255,255,255,.2); color: var(--rail-text-hi);
}
.rail-link {
  background: none; border: 0; font: inherit;
  color: var(--rail-muted); font-size: 12px; cursor: pointer;
  text-align: center; padding: var(--s1) 0;
  transition: color var(--mo-fast);
}
.rail-link:hover { color: var(--rail-text-hi); }

/* ── Zona principal ────────────────────────────────────────────────────────── */

.main { min-width: 0; display: flex; flex-direction: column; }

.top {
  height: var(--top-h); padding: 0 var(--s6);
  display: flex; align-items: center; gap: var(--s3);
  background: var(--surface); border-bottom: 1px solid var(--border);
  position: sticky; top: 0; z-index: 5;
}
@media (max-width: 960px) { .top { padding: 0 var(--s4); } }
.crumb {
  font-size: 12.5px; color: var(--text-muted); min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.crumb b { color: var(--text); font-weight: 600; }
.actions { display: flex; gap: var(--s2); align-items: center; flex-shrink: 0; }

.canvas {
  width: 100%; max-width: var(--canvas-max); margin: 0 auto;
  padding: var(--s5) var(--s6) var(--s9);
}
@media (max-width: 960px) { .canvas { padding: var(--s4) var(--s4) var(--s8); } }

/* Lienzo a sangre: la conversación ocupa el alto entero y gestiona su propio
   desplazamiento. Un chat metido en una página que también hace scroll produce
   dos barras que compiten, y el mensaje que llega empuja la pantalla. */
.canvas.full {
  max-width: none; padding: 0;
  height: calc(100vh - var(--top-h)); min-height: 0;
}
.canvas.full #view { height: 100%; min-height: 0; }

.canvas > .head { margin-bottom: var(--s5); }
.canvas h1 { margin-bottom: var(--s1); }
.sub { color: var(--text-2); font-size: 13px; margin: 0; max-width: 84ch; }
#view { outline: none; }

/* ── Tarjetas ──────────────────────────────────────────────────────────────── */

.card {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r4); box-shadow: var(--shadow-2);
  margin-bottom: var(--s4);
}
.card .hd {
  padding: var(--s3) var(--s4); border-bottom: 1px solid var(--border);
  display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap;
  background: var(--surface);
}
.card .hd h2 { font-size: 13px; font-weight: 620; }
.card .hd .n {
  font-family: var(--mono); font-size: 11px; color: var(--text-2);
  background: var(--surface-2); border: 1px solid var(--border);
  padding: 1px 6px; border-radius: var(--r1);
}
.card .bd { padding: var(--s4); }
/* El recorte va en el cuerpo pegado a los bordes —el que contiene una tabla— y
   no en la tarjeta entera: recortar la tarjeta también recortaría cualquier
   desplegable que una vista abra dentro de ella. */
.card .bd.tight { padding: 0; overflow: hidden; border-radius: 0 0 var(--r4) var(--r4); }
.card .hd:first-child { border-radius: var(--r4) var(--r4) 0 0; }

.panel {
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--r4);
  padding: var(--s7) var(--s6); text-align: center; box-shadow: var(--shadow);
}
.panel h2 { font-size: 15px; margin-bottom: var(--s2); }
.panel p { color: var(--text-2); font-size: 13px; margin: 0 auto; max-width: 68ch; }
.panel .btn { margin-top: var(--s4); }
.panel.deny {
  border-color: color-mix(in srgb, var(--warning) 38%, transparent);
  background: var(--warning-bg);
}

.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: var(--s3); }

/* ── Indicadores ───────────────────────────────────────────────────────────── */

.kpis {
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--s3); margin-bottom: var(--s5);
}
@media (max-width: 1400px) { .kpis { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (max-width: 1040px) { .kpis { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 620px)  { .kpis { grid-template-columns: 1fr; } }

/* Las tarjetas de indicador no llevan sombra: son una franja de lectura, no
   objetos flotantes. La elevación se reserva para lo que de verdad está por
   encima del lienzo. */
.kpi {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r3); padding: var(--s3) var(--s4); min-width: 0;
}
.kpi .l {
  font-size: 12px; font-weight: 500; color: var(--text-2); margin: 0 0 var(--s1);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.kpi .v {
  font-size: 22px; font-weight: 650; letter-spacing: -.028em; line-height: 1.15;
  font-variant-numeric: tabular-nums; overflow: hidden; text-overflow: ellipsis;
}
.kpi .h { font-size: 11.5px; color: var(--text-muted); margin-top: var(--s1); line-height: 1.45; }
.kpi.ok    .v { color: var(--success); }
.kpi.warn  .v { color: var(--warning); }
.kpi.error .v { color: var(--error); }

/* ── Botones ───────────────────────────────────────────────────────────────── */

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  padding: 7px var(--s3); border-radius: var(--r2);
  border: 1px solid var(--border); background: var(--surface); color: var(--text);
  font-family: inherit; font-size: 13px; font-weight: 550; line-height: 1.3;
  cursor: pointer; white-space: nowrap;
  transition: background var(--mo-fast), border-color var(--mo-fast), color var(--mo-fast);
}
.btn:hover { background: var(--surface-2); border-color: var(--border-strong); }
.btn:active { background: var(--surface-3); }
.btn.primary {
  background: var(--accent); border-color: var(--accent); color: #fff; font-weight: 600;
}
.btn.primary:hover { background: var(--accent-hover); border-color: var(--accent-hover); }
.btn.danger { background: var(--error); border-color: var(--error); color: #fff; font-weight: 600; }
.btn.danger:hover { background: color-mix(in srgb, var(--error) 88%, #000); }
.btn.ghost { background: transparent; }
.btn.ghost:hover { background: var(--surface-2); }
.btn.sm { padding: 4px var(--s2); font-size: 12.5px; }
.btn.block { width: 100%; }
.btn:disabled { opacity: .5; cursor: not-allowed; }
.btn svg { width: 14px; height: 14px; }

.iconbtn {
  background: none; border: 0; color: var(--text-muted); font-size: 17px; line-height: 1;
  cursor: pointer; padding: 2px 5px; margin-left: auto; border-radius: var(--r1);
  transition: color var(--mo-fast), background var(--mo-fast);
}
.iconbtn:hover { color: var(--text); background: var(--surface-2); }

/* Botón cuadrado con icono, para las acciones de una fila de conversación. */
.sqbtn {
  display: inline-grid; place-items: center; width: 28px; height: 28px;
  border: 1px solid transparent; border-radius: var(--r2);
  background: none; color: var(--text-muted); cursor: pointer;
  transition: color var(--mo-fast), background var(--mo-fast), border-color var(--mo-fast);
}
.sqbtn:hover { color: var(--text); background: var(--surface-2); border-color: var(--border); }
.sqbtn.danger:hover { color: var(--error); border-color: color-mix(in srgb, var(--error) 40%, transparent); }
.sqbtn svg { width: 15px; height: 15px; }

/* ── Distintivos ───────────────────────────────────────────────────────────── */

.pill {
  display: inline-flex; align-items: center; gap: var(--s1);
  font-size: 11.5px; font-weight: 600; letter-spacing: .005em;
  padding: 2px 7px; border-radius: var(--r1); border: 1px solid;
  white-space: nowrap; line-height: 1.5;
}
.pill.mono { font-family: var(--mono); font-size: 11px; }
.p-ok    { color: var(--success); border-color: color-mix(in srgb, var(--success) 38%, transparent); background: var(--success-bg); }
.p-warn  { color: var(--warning); border-color: color-mix(in srgb, var(--warning) 38%, transparent); background: var(--warning-bg); }
.p-error { color: var(--error);   border-color: color-mix(in srgb, var(--error) 38%, transparent);   background: var(--error-bg); }
.p-info  { color: var(--info);    border-color: color-mix(in srgb, var(--info) 38%, transparent);    background: var(--info-bg); }
.p-muted { color: var(--text-2);  border-color: var(--border); background: var(--surface-2); }

.chip {
  display: inline-flex; align-items: center; gap: var(--s1);
  font-family: var(--mono); font-size: 11.5px; color: var(--text-2);
  background: var(--surface-2); border: 1px solid var(--border);
  padding: 1px 6px; border-radius: var(--r1); white-space: nowrap;
}
.chip svg { width: 12px; height: 12px; }

/* ── Formularios ───────────────────────────────────────────────────────────── */

.field { margin-bottom: var(--s3); }
.field.full { grid-column: 1 / -1; }
label {
  display: block; font-size: 12px; font-weight: 550; color: var(--text-2);
  margin-bottom: var(--s1); letter-spacing: 0;
}
input, select, textarea {
  width: 100%; padding: 7px 10px;
  border: 1px solid var(--border); border-radius: var(--r2);
  background: var(--surface); color: var(--text);
  font-family: var(--sans); font-size: 13px; line-height: 1.4;
  transition: border-color var(--mo-fast), box-shadow var(--mo-fast);
}
input.mono, textarea.mono, select.mono { font-family: var(--mono); font-size: 12.5px; }
textarea { min-height: 96px; line-height: 1.55; resize: vertical; }
input:hover, select:hover, textarea:hover { border-color: var(--border-strong); }
input:focus, select:focus, textarea:focus {
  outline: none; border-color: var(--accent); box-shadow: var(--ring);
}
input:disabled, select:disabled, textarea:disabled {
  background: var(--surface-2); color: var(--text-muted); cursor: not-allowed;
}
select { appearance: auto; }

.check { display: flex; align-items: flex-start; gap: var(--s2); }
.check input { width: auto; margin-top: 3px; }
.check label { font-size: 13px; font-weight: 500; color: var(--text); margin: 0; }
.hint { font-size: 12px; color: var(--text-muted); margin: var(--s1) 0 0; line-height: 1.45; }
.hint code, .hint .mono { font-family: var(--mono); font-size: 11.5px; }

.uf-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); gap: 0 var(--s4); }
.uf-err {
  margin: var(--s3) 0 0; padding: var(--s3); border-radius: var(--r2); font-size: 13px;
  border: 1px solid color-mix(in srgb, var(--error) 32%, transparent);
  background: var(--error-bg); color: var(--text-2);
}
.uf-foot { display: flex; gap: var(--s2); align-items: center; margin-top: var(--s4); flex-wrap: wrap; }

.toolbar {
  display: flex; align-items: flex-end; gap: var(--s3); flex-wrap: wrap;
  padding: var(--s3) var(--s4); border-bottom: 1px solid var(--border);
  background: var(--surface-2);
}
.toolbar .field { margin: 0; min-width: 160px; }
.toolbar .grow { flex: 1; min-width: 200px; }

/* ── Tablas ────────────────────────────────────────────────────────────────── */

.tw { overflow-x: auto; }
table.t { width: 100%; border-collapse: collapse; font-size: 13px; }
table.t caption {
  caption-side: top; text-align: left; font-size: 12px; color: var(--text-muted);
  padding: var(--s3) var(--s4) var(--s2);
}
table.t th {
  text-align: left; font-size: 12px; font-weight: 600; color: var(--text-2);
  padding: var(--s2) var(--s4); white-space: nowrap;
  background: var(--surface-2); border-bottom: 1px solid var(--border);
  position: sticky; top: 0; z-index: 1;
}
table.t th.right { text-align: right; }
table.t td { padding: 7px var(--s4); border-bottom: 1px solid var(--border); vertical-align: middle; }
table.t td.right { text-align: right; }
table.t td.mono { font-family: var(--mono); font-size: 12px; }
table.t tbody tr { transition: background var(--mo-fast); }
table.t tbody tr:hover { background: var(--surface-2); }
table.t tbody tr:last-child td { border-bottom: 0; }
table.t tbody tr.clickable { cursor: pointer; }
table.t tbody tr.clickable:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
table.t tbody tr.is-empty:hover { background: transparent; }

th .sortbtn {
  display: inline-flex; align-items: center; gap: var(--s1);
  background: none; border: 0; padding: 0; font: inherit; color: inherit;
  cursor: pointer; transition: color var(--mo-fast);
}
th .sortbtn:hover { color: var(--text); }
th .arrow { font-size: 8px; opacity: .3; }
th[aria-sort] .arrow { opacity: 1; color: var(--accent); }
th[aria-sort] { color: var(--text); }

.pager {
  display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap;
  padding: var(--s2) var(--s4); border-top: 1px solid var(--border);
  background: var(--surface-2); font-size: 12.5px; color: var(--text-2);
}
.pager .grow { flex: 1; }

/* ── Estados vacíos ────────────────────────────────────────────────────────── */

.empty { padding: var(--s7) var(--s5); text-align: center; }
.empty > svg {
  width: 28px; height: 28px; color: var(--text-muted); opacity: .7;
  margin: 0 auto var(--s3); display: block;
}
.empty .et { font-size: 13.5px; font-weight: 600; margin: 0 0 var(--s1); color: var(--text); }
.empty .eh { font-size: 12.5px; color: var(--text-2); margin: 0 auto; max-width: 62ch; line-height: 1.5; }
.empty .btn { margin-top: var(--s4); }

.skel {
  display: block; height: 11px; border-radius: var(--r1); background: var(--surface-2);
  animation: pulse 1.5s ease-in-out infinite;
}
@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: .5; } }

/* ── Gráficos ──────────────────────────────────────────────────────────────── */

.chart-host { width: 100%; min-width: 0; }
.chart { width: 100%; height: auto; display: block; overflow: visible; }
.chart .ax-line { stroke: var(--border); stroke-width: 1; }
.chart .ax-base { stroke: var(--border-strong); stroke-width: 1; }
.chart .ax-text { fill: var(--text-muted); font-family: var(--mono); font-size: 10px; }
.chart .ax-text.mid { text-anchor: middle; }
.chart .ax-text.end { text-anchor: end; }
.chart .guide { stroke: var(--border-strong); stroke-width: 1; stroke-dasharray: 3 3; }
.chart .serie { fill: none; stroke-width: 1.75; stroke-linejoin: round; stroke-linecap: round; }
/* El área bajo la línea da volumen sin competir con ella: a más del 10 % de
   opacidad la serie deja de leerse y el gráfico se vuelve decorativo. */
.chart .area { stroke: none; fill-opacity: .08; }
.chart .dot { stroke: var(--surface); stroke-width: 2; }
.chart .marker { stroke: var(--surface); stroke-width: 1.5; }
.chart .bar { transition: opacity var(--mo-fast); }
.chart .bar:hover { opacity: .78; }
.chart .tip { pointer-events: none; }
.chart .tip-bg { fill: var(--surface); stroke: var(--border-strong); stroke-width: 1; }
.chart .tip-t { fill: var(--text); font-family: var(--mono); font-size: 10.5px; }
.chart .tip-t.mute { fill: var(--text-muted); }

.chart-empty {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: var(--s2); min-height: 148px; padding: var(--s6) var(--s5); text-align: center;
  background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--r3);
}
.chart-empty svg { width: 26px; height: 26px; color: var(--text-muted); opacity: .7; }
.chart-empty .et { font-size: 13px; font-weight: 600; margin: 0; color: var(--text); }
.chart-empty .eh { font-size: 12.5px; color: var(--text-2); margin: 0; max-width: 56ch; line-height: 1.5; }

.chart-legend {
  display: flex; gap: var(--s4); flex-wrap: wrap; margin-top: var(--s2);
  font-size: 12px; color: var(--text-2);
}
.chart-legend span.sw {
  display: inline-block; width: 10px; height: 3px; border-radius: 2px;
  vertical-align: middle; margin-right: 6px;
}

/* ── Avisos ────────────────────────────────────────────────────────────────── */

.toasts {
  position: fixed; top: var(--s3); right: var(--s3);
  display: flex; flex-direction: column; gap: var(--s2);
  z-index: 60; max-width: min(400px, calc(100vw - var(--s6)));
}
.toast {
  background: var(--surface); border: 1px solid var(--border); border-left-width: 3px;
  border-radius: var(--r3); box-shadow: var(--shadow-lg);
  padding: var(--s3); font-size: 13px; color: var(--text-2);
  display: flex; gap: var(--s2); align-items: flex-start;
}
.toast b { color: var(--text); font-weight: 600; }
.toast.ok    { border-left-color: var(--success); }
.toast.warn  { border-left-color: var(--warning); }
.toast.error { border-left-color: var(--error); }
.toast.info  { border-left-color: var(--accent); }
.toast .x {
  background: none; border: 0; color: var(--text-muted); cursor: pointer;
  font-size: 15px; line-height: 1; padding: 0; margin-left: auto;
}
.toast .x:hover { color: var(--text); }

.notice {
  border-radius: var(--r3); padding: var(--s3) var(--s4); font-size: 13px;
  color: var(--text-2); margin-bottom: var(--s4);
  border: 1px solid var(--border); background: var(--surface-2);
}
.notice b { color: var(--text); }
.notice.warn  { border-color: color-mix(in srgb, var(--warning) 32%, transparent); background: var(--warning-bg); }
.notice.error { border-color: color-mix(in srgb, var(--error) 32%, transparent);   background: var(--error-bg); }
.notice.info  { border-color: color-mix(in srgb, var(--info) 32%, transparent);    background: var(--info-bg); }

/* ── Capas: modal y cajón ──────────────────────────────────────────────────── */

/* La capa aparecía de golpe, y aparecer de golpe se lee como un fallo del
   navegador: era literalmente la queja —«se ve que hay error»—. Con un velo que
   entra en 140 ms y un panel que sube 8 px en 180 ms, el ojo entiende que algo
   se ha abierto encima y no que la pantalla ha parpadeado.

   Los tiempos son cortos a propósito: es un formulario que un operador abre
   veinte veces al día, y por encima de 200 ms una animación deja de informar y
   empieza a estorbar. El bloque de movimiento reducido del principio de la hoja
   ya las desactiva para quien las haya pedido desactivadas. */
@keyframes scrim-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes modal-in {
  from { opacity: 0; transform: translateY(8px) scale(.985); }
  to   { opacity: 1; transform: none; }
}
@keyframes drawer-in {
  from { opacity: 0; transform: translateX(28px); }
  to   { opacity: 1; transform: none; }
}

.scrim {
  position: fixed; inset: 0; background: rgba(3,20,47,.42);
  -webkit-backdrop-filter: blur(2px); backdrop-filter: blur(2px);
  z-index: 40; display: grid; place-items: center; padding: var(--s5);
  animation: scrim-in .14s ease-out both;
}
.modal {
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--r5);
  /* Al relieve se le suma un filo de un píxel: sobre fondo oscuro el borde solo
     no separa el panel del velo, y el diálogo parecía flotar sin contorno. */
  box-shadow: var(--shadow-lg), 0 0 0 1px color-mix(in srgb, var(--border) 55%, transparent);
  width: 100%; max-width: 540px; max-height: 86vh;
  display: flex; flex-direction: column; overflow: hidden;
  animation: modal-in .18s cubic-bezier(.16, 1, .3, 1) both;
}
.modal .hd, .drawer .hd {
  padding: var(--s4) var(--s5); border-bottom: 1px solid var(--border);
  display: flex; align-items: center; gap: var(--s3);
}
.modal .hd h2, .drawer .hd h2 { font-size: 14px; }
.modal .bd {
  padding: var(--s5); overflow-y: auto; color: var(--text-2); font-size: 13px;
}
/* El pie se asienta sobre el tono secundario: separa la zona de decisión del
   contenido sin necesidad de una segunda línea divisoria. */
.modal .ft {
  padding: var(--s4) var(--s5); border-top: 1px solid var(--border);
  background: var(--surface-2);
  display: flex; gap: var(--s3); justify-content: flex-end; flex-wrap: wrap;
}

/* `right` aquí significa «pegado al borde derecho», pero el mismo nombre existe
   como utilidad de texto —`.right { text-align: right; }`— y esa declaración se
   heredaba a todo el cajón: etiquetas, ayudas y párrafos salían alineados a la
   derecha. Se devuelve la alineación al valor natural del documento; el nombre
   de la clase se deja como está para no tocar el marcado de todas las vistas. */
.scrim.right { place-items: stretch; justify-content: flex-end; padding: 0; text-align: initial; }
.drawer {
  background: var(--surface); border-left: 1px solid var(--border);
  width: min(620px, 100vw); height: 100vh; display: flex; flex-direction: column;
  box-shadow: var(--shadow-lg);
  animation: drawer-in .2s cubic-bezier(.16, 1, .3, 1) both;
}
.drawer .bd { padding: var(--s5); overflow-y: auto; flex: 1; }

/* ── Definiciones clave/valor y medidor ────────────────────────────────────── */

.kv {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: var(--s3) var(--s5); margin: 0;
}
.kv div { min-width: 0; }
.kv dt { font-size: 12px; font-weight: 500; color: var(--text-2); }
.kv dd { margin: 2px 0 0; font-size: 13px; word-break: break-word; }
.kv dd .mono { font-family: var(--mono); font-size: 12.5px; }

.meter { height: 6px; border-radius: 3px; background: var(--surface-3); overflow: hidden; margin-top: var(--s1); }
.meter > i { display: block; height: 100%; background: var(--accent); border-radius: 3px; }
.meter.warn  > i { background: var(--warning); }
.meter.error > i { background: var(--error); }

/* ══════════════════════════════════════════════════════════════════════════════
   Conversación

   Es la pantalla donde se juega la comparación con las consolas que el usuario
   ya conoce, y por eso tiene su propio bloque: nada de lo que hay aquí se
   reutiliza en las otras cuatro secciones, y mezclarlo con el sistema general
   haría creer que sí.
   ══════════════════════════════════════════════════════════════════════════════ */

.chat {
  display: grid; grid-template-columns: 280px minmax(0, 1fr);
  height: 100%; min-height: 0;
}
@media (max-width: 900px) { .chat { grid-template-columns: 1fr; } }

/* ── Columna de conversaciones ─────────────────────────────────────────────── */

.chat-side {
  border-right: 1px solid var(--border); background: var(--surface);
  display: flex; flex-direction: column; min-height: 0;
}
@media (max-width: 900px) {
  /* En pantalla estrecha la lista se pliega a una altura fija: el hilo es lo
     que se viene a leer, y una lista de veinte conversaciones empujándolo hacia
     abajo lo convierte en la segunda cosa de la pantalla. */
  .chat-side { border-right: 0; border-bottom: 1px solid var(--border); max-height: 34vh; }
}
.chat-side .hd { padding: var(--s3); border-bottom: 1px solid var(--border); }
.chat-side .filters {
  display: flex; gap: var(--s1); padding: var(--s2) var(--s3);
  border-bottom: 1px solid var(--border); background: var(--surface-2);
}
.chat-side .filters button {
  flex: 1; padding: 3px var(--s2); border: 1px solid transparent; border-radius: var(--r1);
  background: none; color: var(--text-2); font: inherit; font-size: 12px; cursor: pointer;
  transition: background var(--mo-fast), color var(--mo-fast);
}
.chat-side .filters button:hover { background: var(--surface); color: var(--text); }
.chat-side .filters button[aria-pressed="true"] {
  background: var(--surface); border-color: var(--border); color: var(--text); font-weight: 600;
}

.conv-list { overflow-y: auto; flex: 1; min-height: 0; padding: var(--s2); }
.conv {
  display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center;
  gap: var(--s1); width: 100%; text-align: left;
  padding: var(--s2); border: 1px solid transparent; border-radius: var(--r3);
  background: none; color: inherit; font: inherit; cursor: pointer;
  transition: background var(--mo-fast), border-color var(--mo-fast);
}
.conv:hover { background: var(--surface-2); }
.conv[aria-current="true"] {
  background: var(--surface-2); border-color: var(--border);
}
.conv .ct {
  font-size: 13px; font-weight: 550; margin: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.conv .cm {
  grid-column: 1 / -1; font-size: 11.5px; color: var(--text-muted); margin: 2px 0 0;
  display: flex; gap: var(--s2); align-items: center;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.conv .cm .dot { width: 3px; height: 3px; border-radius: 50%; background: currentColor; opacity: .6; }
/* El objetivo se distingue por color de texto y no por un icono más: en una
   lista de treinta filas, treinta iconos son ruido y una palabra es un dato. */
.conv .tgt { color: var(--accent); font-weight: 600; }
.conv .tgt.model { color: var(--text-2); }

/* ── Hilo ──────────────────────────────────────────────────────────────────── */

.chat-main { display: flex; flex-direction: column; min-height: 0; min-width: 0; }

.chat-head {
  display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap;
  padding: var(--s3) var(--s5); border-bottom: 1px solid var(--border);
  background: var(--surface);
}
.chat-head h2 {
  font-size: 14px; font-weight: 620; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.chat-head .grow { flex: 1; min-width: 0; }

/* Franja de coste. Siempre a la vista y pegada bajo la cabecera: un usuario que
   ve lo que gasta se autorregula, y el gasto de una conversación es cuadrático
   —el historial se reenvía entero en cada vuelta—, así que esconderlo hasta la
   factura es exactamente el error que esta pantalla viene a corregir. */
.costbar {
  display: flex; align-items: center; gap: var(--s4); flex-wrap: wrap;
  padding: var(--s2) var(--s5); border-bottom: 1px solid var(--border);
  background: var(--surface-2); font-size: 12px; color: var(--text-2);
}
.costbar .m { display: inline-flex; align-items: baseline; gap: var(--s1); }
.costbar .m b {
  font-family: var(--mono); font-variant-numeric: tabular-nums;
  color: var(--text); font-weight: 600; font-size: 12.5px;
}
.costbar .src { margin-left: auto; color: var(--text-muted); font-size: 11.5px; }
.costbar .win { color: var(--text-muted); }
.costbar .win.trim { color: var(--warning); }

.thread {
  flex: 1; min-height: 0; overflow-y: auto;
  padding: var(--s5) var(--s5) var(--s6);
  scroll-behavior: smooth;
}
.thread-inner { max-width: 860px; margin: 0 auto; display: flex; flex-direction: column; gap: var(--s5); }

.msg { display: flex; flex-direction: column; gap: var(--s2); min-width: 0; }
.msg .who {
  display: flex; align-items: center; gap: var(--s2);
  font-size: 11.5px; color: var(--text-muted);
}
.msg .who b { color: var(--text-2); font-weight: 600; font-size: 12px; }

/* El turno del usuario va en una superficie propia y alineado a la derecha; el
   del asistente ocupa el ancho de lectura sin caja. Es la asimetría que hace que
   se distingan de un vistazo sin colorear medio hilo. */
.msg.user .who { justify-content: flex-end; }
.msg.user .body {
  align-self: flex-end; max-width: min(80%, 62ch);
  background: var(--surface-2); border: 1px solid var(--border);
  border-radius: var(--r4) var(--r4) var(--r1) var(--r4);
  padding: var(--s3) var(--s4);
}
.msg.assistant .body { max-width: 78ch; }
.msg .body { font-size: 14px; line-height: 1.62; white-space: pre-wrap; word-break: break-word; }
.msg .body p { margin: 0 0 var(--s3); }
.msg .body p:last-child { margin-bottom: 0; }

/* Markdown ya interpretado. El `pre-wrap` sirve mientras el modelo escribe;
   después los saltos son elementos y mantenerlo duplicaría cada separación. */
.msg .body.md { white-space: normal; }
.msg .body .md-h {
  font-size: 14.5px; font-weight: 650; line-height: 1.35;
  margin: var(--s4) 0 var(--s2); color: var(--text);
}
.msg .body .md-h:first-child { margin-top: 0; }
.msg .body .md-list { margin: 0 0 var(--s3); padding-left: 1.35em; }
.msg .body .md-list:last-child { margin-bottom: 0; }
.msg .body .md-list li { margin: 0 0 calc(var(--s1) / 1.5); }
.msg .body .md-list li:last-child { margin-bottom: 0; }
.msg .body .md-list ul, .msg .body .md-list ol { margin-top: calc(var(--s1) / 1.5); }
.msg .body .md-inline {
  font-family: var(--mono, ui-monospace, SFMono-Regular, Menlo, monospace);
  font-size: 0.9em; padding: 0.1em 0.34em; border-radius: var(--r1, 4px);
  background: var(--surface-2, rgba(127, 127, 127, 0.12));
}
.msg .body .md-code {
  margin: 0 0 var(--s3); padding: var(--s3); overflow-x: auto;
  border: 1px solid var(--border); border-radius: var(--r2, 6px);
  background: var(--surface-2, rgba(127, 127, 127, 0.08));
}
.msg .body .md-code:last-child { margin-bottom: 0; }
.msg .body .md-code code {
  font-family: var(--mono, ui-monospace, SFMono-Regular, Menlo, monospace);
  font-size: 12.5px; line-height: 1.5; white-space: pre;
}
.msg .body .md-quote {
  margin: 0 0 var(--s3); padding: var(--s1) 0 var(--s1) var(--s3);
  border-left: 2px solid var(--border); color: var(--text-2);
}
.msg .body .md-hr { border: 0; border-top: 1px solid var(--border); margin: var(--s4) 0; }
.msg .body a { color: var(--accent, currentColor); text-decoration: underline; }
.msg.error .body {
  color: var(--text-2); border: 1px solid color-mix(in srgb, var(--error) 32%, transparent);
  background: var(--error-bg); border-radius: var(--r3); padding: var(--s3) var(--s4);
}

/* Cursor de generación. Es un bloque que parpadea; no hay puntos saltando ni
   ninguna otra animación que compita con el texto que está llegando. */
.caret {
  display: inline-block; width: 7px; height: 1.05em; margin-left: 2px;
  background: var(--accent); vertical-align: text-bottom;
  animation: blink 1s steps(2, start) infinite;
}
@keyframes blink { 50% { opacity: 0; } }

/* ── Razonamiento, herramientas y citas ────────────────────────────────────── */

/* Los tres se pliegan por defecto y comparten forma: son el andamiaje de la
   respuesta, no la respuesta. Quien quiera auditarla los abre. */
.fold {
  border: 1px solid var(--border); border-radius: var(--r3);
  background: var(--surface); overflow: hidden;
}
.fold + .fold { margin-top: var(--s2); }
.fold > summary {
  display: flex; align-items: center; gap: var(--s2);
  padding: var(--s2) var(--s3); cursor: pointer; list-style: none;
  font-size: 12.5px; color: var(--text-2); background: var(--surface-2);
}
.fold > summary::-webkit-details-marker { display: none; }
.fold > summary:hover { color: var(--text); }
.fold > summary svg { width: 14px; height: 14px; flex-shrink: 0; opacity: .8; }
.fold > summary b { color: var(--text); font-weight: 600; }
.fold > summary .n { margin-left: auto; color: var(--text-muted); font-size: 11.5px; }
.fold .fb { padding: var(--s3); font-size: 12.5px; color: var(--text-2); }
.fold pre {
  font-family: var(--mono); font-size: 11.5px; color: var(--text-2);
  background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--r2);
  padding: var(--s2) var(--s3); margin: var(--s2) 0 0;
  white-space: pre-wrap; word-break: break-word; overflow-x: auto; max-height: 320px;
}

.tool { border-bottom: 1px solid var(--border); padding: var(--s3); }
.tool:last-child { border-bottom: 0; }
.tool .tn {
  display: flex; align-items: center; gap: var(--s2);
  font-size: 12.5px; font-weight: 600; color: var(--text);
}
.tool .tn .ms { margin-left: auto; font-family: var(--mono); font-size: 11px; color: var(--text-muted); font-weight: 400; }
.tool .lbl { font-size: 11px; color: var(--text-muted); margin: var(--s2) 0 0; text-transform: none; }

/* Citas. Un banco no acepta una respuesta sin saber de dónde salió, así que la
   cita no se pliega dentro del razonamiento: va debajo de la respuesta, visible,
   con documento y página. Lo que se pliega es el fragmento literal. */
.cites { display: flex; flex-direction: column; gap: var(--s2); }
.cites .ct {
  display: flex; align-items: center; gap: var(--s2);
  font-size: 11.5px; color: var(--text-muted);
}
.cite {
  border: 1px solid var(--border); border-left: 2px solid var(--accent);
  border-radius: var(--r2); background: var(--surface); padding: var(--s2) var(--s3);
}
.cite .doc { font-size: 12.5px; font-weight: 600; color: var(--text); }
.cite .doc .frag { font-weight: 400; color: var(--text-2); }
.cite .loc { font-size: 11.5px; color: var(--text-2); margin-top: 1px; }
.cite .ex {
  font-size: 12px; color: var(--text-2); margin-top: var(--s2);
  border-left: 2px solid var(--border); padding-left: var(--s3); line-height: 1.55;
}

/* ── Redacción del turno ───────────────────────────────────────────────────── */

.composer {
  border-top: 1px solid var(--border); background: var(--surface);
  padding: var(--s3) var(--s5) var(--s4);
}
.composer-inner { max-width: 860px; margin: 0 auto; }
.composer .box {
  display: flex; align-items: flex-end; gap: var(--s2);
  border: 1px solid var(--border); border-radius: var(--r4);
  background: var(--surface); padding: var(--s2);
  transition: border-color var(--mo-fast), box-shadow var(--mo-fast);
}
.composer .box:focus-within { border-color: var(--accent); box-shadow: var(--ring); }
.composer textarea {
  flex: 1; border: 0; background: none; padding: var(--s2);
  min-height: 24px; max-height: 220px; resize: none; font-size: 14px; line-height: 1.55;
}
.composer textarea:focus { outline: none; box-shadow: none; }
.composer .send {
  display: inline-grid; place-items: center; width: 34px; height: 34px; flex-shrink: 0;
  border: 0; border-radius: var(--r3); background: var(--accent); color: #fff;
  cursor: pointer; transition: background var(--mo-fast);
}
.composer .send:hover { background: var(--accent-hover); }
.composer .send:disabled { background: var(--disabled); cursor: not-allowed; }
.composer .send svg { width: 17px; height: 17px; }
.composer .send.stop { background: var(--error); }
.composer .send.stop:hover { background: color-mix(in srgb, var(--error) 88%, #000); }
.composer .foot {
  display: flex; align-items: center; gap: var(--s3); flex-wrap: wrap;
  margin-top: var(--s2); font-size: 11.5px; color: var(--text-muted);
}
.composer .foot .opt { display: inline-flex; align-items: center; gap: var(--s1); cursor: pointer; }
.composer .foot .opt input { width: auto; margin: 0; }
.composer .foot kbd {
  font-family: var(--mono); font-size: 10.5px; border: 1px solid var(--border);
  border-bottom-width: 2px; border-radius: var(--r1); padding: 0 4px; color: var(--text-2);
}

/* Botón "imagen" del compositor: mismo tamaño que enviar, sin su color de
   acento — crear una imagen no es enviar el turno, y no debe competir
   visualmente con la acción principal. */
.composer .img-btn {
  display: inline-grid; place-items: center; width: 34px; height: 34px; flex-shrink: 0;
  border: 1px solid var(--border); border-radius: var(--r3); background: var(--surface);
  color: var(--text-2); cursor: pointer; transition: border-color var(--mo-fast), color var(--mo-fast);
}
.composer .img-btn:hover { border-color: var(--border-strong); color: var(--text); }
.composer .img-btn svg { width: 16px; height: 16px; }
.composer-inner { position: relative; }
.img-popover {
  position: absolute; bottom: calc(100% + var(--s2)); left: 0; right: 0;
  border: 1px solid var(--border); border-radius: var(--r4); background: var(--surface);
  box-shadow: var(--shadow); padding: var(--s3); display: flex; flex-direction: column; gap: var(--s2);
}
.img-popover textarea {
  width: 100%; min-height: 56px; resize: vertical; font: inherit; font-size: 13px;
  padding: var(--s2); border: 1px solid var(--border); border-radius: var(--r3); background: var(--surface-2);
}
.img-popover .row { display: flex; gap: var(--s2); align-items: center; }
.img-popover .err { color: var(--error); font-size: 12px; margin: 0; }

/* La miniatura de una imagen generada, dentro de un turno o de una fila de
   herramienta: reemplaza el JSON crudo por algo que se puede mirar. */
.msg-image { max-width: 220px; border-radius: var(--r3); overflow: hidden; border: 1px solid var(--border); }
.msg-image img { display: block; width: 100%; cursor: zoom-in; }
.msg-image .cap { padding: var(--s2); font-size: 11.5px; color: var(--text-2); }

/* ── Elección de con quién se habla ────────────────────────────────────────── */

/* Es la decisión que el usuario no conoce —un agente lleva instrucciones,
   conocimiento y herramientas; un modelo no lleva nada— y por eso ocupa la
   pantalla en lugar de esconderse en un desplegable. Se elige una vez, al abrir
   la conversación, y ya no se vuelve a preguntar. */
.pick { max-width: 720px; margin: 0 auto; padding: var(--s7) var(--s5); }
.pick > h2 { font-size: 17px; margin-bottom: var(--s1); }
.pick > .sub { margin-bottom: var(--s5); }
.pick-cards { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s3); }
@media (max-width: 700px) { .pick-cards { grid-template-columns: 1fr; } }

.pickcard {
  text-align: left; padding: var(--s4); border: 1px solid var(--border);
  border-radius: var(--r4); background: var(--surface); cursor: pointer;
  font: inherit; color: inherit;
  transition: border-color var(--mo-fast), background var(--mo-fast);
}
.pickcard:hover { border-color: var(--border-strong); background: var(--surface-2); }
.pickcard[aria-pressed="true"] {
  border-color: var(--accent);
  box-shadow: inset 0 0 0 1px var(--accent);
}
.pickcard .ph { display: flex; align-items: center; gap: var(--s2); }
.pickcard .ph svg { width: 18px; height: 18px; color: var(--accent); }
.pickcard .ph b { font-size: 14px; font-weight: 620; }
.pickcard p { margin: var(--s2) 0 0; font-size: 12.5px; color: var(--text-2); line-height: 1.5; }

.pick .choice { margin-top: var(--s5); }
.pick .choice .field { max-width: 420px; }
.pick .go { margin-top: var(--s4); }

/* ══════════════════════════════════════════════════════════════════════════════
   PUERTA DE ACCESO · las cinco capas del manual

   La lámina era una superficie sólida sobre un fondo sólido, y por eso se leía
   plana: el manual describe el canvas por capas —casi negro, rejilla ámbar al
   3 %, un blob que respira, vidrio, ruido al 1,5 %— y advierte que el vidrio
   sobre oscuro plano se ve muerto. Faltaba el blob, así que faltaba todo.

   El tratamiento es distinto en claro y en oscuro, y no por gusto: el vidrio es
   blanco sobre blanco cuando el fondo ya es claro, de modo que ahí el mismo
   papel lo hace un halo tenue y una sombra bien puesta. La marca vive en el
   canvas oscuro; en claro se comporta.
   ══════════════════════════════════════════════════════════════════════════════ */

.gate { position: fixed; isolation: isolate; }

/* Capa 2 · rejilla de 60 px, enmascarada hacia el centro para que no empape los
   bordes de la pantalla. */
.gate::before {
  content: ""; position: absolute; inset: 0; z-index: 0; pointer-events: none;
  background:
    linear-gradient(rgba(245,158,11,.05) 1px, transparent 1px) 0 0 / 100% 60px,
    linear-gradient(90deg, rgba(245,158,11,.05) 1px, transparent 1px) 0 0 / 60px 100%;
  mask-image: radial-gradient(90% 70% at 50% 0%, #000 0%, #000 45%, transparent 100%);
}

/* Capa 3 · el blob. Respira en 9 s, que es el vocabulario de movimiento de la
   marca: lento, ambiental, deliberado. */
.gate::after {
  content: ""; position: absolute; z-index: 0; pointer-events: none;
  width: 70vw; height: 70vw; min-width: 560px; min-height: 560px;
  top: -30vw; left: -12vw;
  background: radial-gradient(circle at 50% 50%,
    rgba(245,158,11,.16) 0%, rgba(245,158,11,.06) 40%, transparent 70%);
  filter: blur(46px);
}
:root[data-theme="light"] .gate::after {
  background: radial-gradient(circle at 50% 50%,
    rgba(245,158,11,.14) 0%, rgba(245,158,11,.05) 42%, transparent 72%);
}

.gate-shell, .gate-bar, .gate-card { position: relative; z-index: 1; }

/* Capa 4 · vidrio. Fondo blanco al 3 %, desenfoque de 20 px, borde al 6 %: las
   cifras del manual, no una aproximación. */
:root[data-theme="dark"] .gate-shell {
  background: rgba(255,255,255,.03);
  backdrop-filter: blur(20px);
  border-color: rgba(255,255,255,.06);
  box-shadow: 0 40px 80px -40px rgba(0,0,0,.9);
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .gate-shell {
    background: rgba(255,255,255,.03);
    backdrop-filter: blur(20px);
    border-color: rgba(255,255,255,.06);
    box-shadow: 0 40px 80px -40px rgba(0,0,0,.9);
  }
}

/* La tarjeta del formulario es la superficie ACTIVA de la pantalla: es donde se
   escribe. Lleva el filete superior en degradado, que es firma de marca. */
.gate-card::before {
  content: ""; position: absolute; top: -1px; left: 22px; right: 22px; height: 1px;
  background: linear-gradient(90deg, transparent, rgba(245,158,11,.6), transparent);
}
:root[data-theme="dark"] .gate-card {
  background: rgba(255,255,255,.05);
  backdrop-filter: blur(12px);
  border-color: rgba(255,255,255,.08);
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .gate-card {
    background: rgba(255,255,255,.05);
    backdrop-filter: blur(12px);
    border-color: rgba(255,255,255,.08);
  }
}

/* El titular de la tarjeta a la escala de la marca: Inter 700 con tracking
   cerrado. A 1,5 rem competía con la reclamación de la izquierda en vez de
   mandar sobre su propia mitad. */
.gate-card h1 { font-size: 1.7rem; letter-spacing: -.03em; }

/* La reclamación de marca, a la escala de sección del manual. */
.gate-claim { letter-spacing: -.028em; }

/* Capa 5 · ruido. Sin él, el degradado del blob se bandea en pantallas de 8 bits
   y se ven aros concéntricos donde debería haber una transición limpia. */
.gate-noise {
  position: absolute; inset: 0; z-index: 2; pointer-events: none;
  opacity: .015;
}
:root[data-theme="light"] .gate-noise { opacity: .01; }

@media (prefers-reduced-motion: no-preference) {
  .gate::after { animation: gate-breathe 9s ease-in-out infinite; }
  @keyframes gate-breathe {
    0%, 100% { transform: scale(1); opacity: .85; }
    50%      { transform: scale(1.09) translate(2%, -2%); opacity: 1; }
  }
  /* Entrada de la lámina: una sola, deliberada, con salida exponencial. */
  .gate-shell { animation: gate-in .8s cubic-bezier(.16,1,.3,1) both; }
  @keyframes gate-in {
    from { opacity: 0; transform: translateY(16px); }
    to   { opacity: 1; transform: none; }
  }
}

/* ── Lockup de la puerta ─────────────────────────────────────────────────────
   Sustituye al PNG. Un raster de 640×207 servido a 184 px dejaba el descriptor
   incrustado en seis píxeles de altura: el manual lo prohíbe expresamente —por
   debajo del umbral se cambia de variante, nunca se escala la pieza— y en
   pantalla se leía como una mancha. Compuesto, cada parte tiene su tamaño y su
   peso, y es nítido en cualquier densidad. */
.gate-logo {
  flex: none; width: auto; aspect-ratio: auto;
  background: none;
  display: flex; align-items: center; gap: 13px;
}
.gate-mark { width: 40px; flex: none; color: var(--text); }
.gate-mark svg { display: block; width: 100%; height: auto; }
.gate-word { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.gate-word b {
  font-size: 1.55rem; font-weight: 700; letter-spacing: -.03em;
  line-height: 1; color: var(--text);
}
/* Mono en el descriptor: es una etiqueta, que es uno de los tres trabajos que
   el manual le reserva. */
.gate-word i {
  font-family: var(--mono); font-style: normal;
  font-size: .625rem; font-weight: 500; letter-spacing: .15em;
  text-transform: uppercase; color: var(--text-muted);
}
:root[data-theme="dark"] .gate-logo { background-image: none; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .gate-logo { background-image: none; }
}

/* El filete superior de la tarjeta se leía como un borde más. Se le da cuerpo
   y se le acorta el recorrido para que sea lo que es: una firma, no un marco. */
.gate-card::before {
  top: -1px; left: 30%; right: 30%; height: 2px;
  background: linear-gradient(90deg, transparent, var(--accent), transparent);
  opacity: .85;
}

/* La lámina no cabía en pantallas bajas y se comía el pie. Con el alto mínimo
   liberado, el contenedor desplaza en lugar de recortar: una pantalla de acceso
   que esconde el aviso legal por debajo del borde es una pantalla incompleta. */
.gate { place-items: start center; padding-block: var(--s6); }
.gate-shell { margin-block: auto; }

/* ── Panel de marca sobre imagen ─────────────────────────────────────────────
   El panel deja de seguir al tema y pasa a ser oscuro siempre. No es capricho:
   la imagen ES oscura, y un panel que en claro pinta el fondo a #FDFCFB y en
   oscuro a casi negro dejaría la mitad de los casos con texto negro sobre un
   cielo azul marino. Se fija el lienzo y se fija la paleta que lo acompaña.

   La paleta se redefine como TOKENS sobre el propio panel en lugar de repintar
   cada regla hija. Así el lockup, el filete, el titular, el párrafo y las tres
   garantías cambian de color sin tocar ni una de sus reglas, y el día que se
   añada una pieza al panel hereda lo correcto sin que nadie se acuerde. */
.gate-aside {
  --text:       #F9FAFB;
  --text-2:     #D1D5DB;
  --text-muted: #9CA3AF;
  --accent:     #F59E0B;
  --border:     rgba(255,255,255,.10);
  background: #030712;
  border-right-color: rgba(255,255,255,.08);
  isolation: isolate;
}

/* La imagen va en un pseudoelemento y no en background del panel porque la
   variante «tinta» le aplica un filter, y un filter sobre el panel arrastraría
   también al texto. Separadas, se trata el fondo sin tocar el contenido.

   El encuadre no está centrado: la pieza que importa —el cubo— vive en el
   tercio derecho del archivo, y con cover sobre un panel más alto que ancho un
   centrado la dejaría fuera del recorte. */
.gate-aside::before {
  content: ""; position: absolute; inset: 0; z-index: 0; pointer-events: none;
  background: url("/assets/gate-bg.webp") 88% 40% / cover no-repeat;
}

/* Velo de legibilidad, y hace falta en las DOS variantes. El archivo tiene un
   resplandor ámbar muy claro en la esquina superior izquierda, que es justo
   donde caen el logotipo y el titular. Sin velo, «IA privada.» se lee sobre su
   propio color de acento. El degradado es diagonal: opaco donde hay texto,
   casi transparente sobre el cubo. */
.gate-aside::after {
  background:
    linear-gradient(to top,
      rgba(3,7,18,.92) 0%,
      rgba(3,7,18,.72) 14%,
      rgba(3,7,18,.28) 26%,
      transparent 38%),
    linear-gradient(107deg,
      rgba(3,7,18,.94) 0%,
      rgba(3,7,18,.86) 26%,
      rgba(3,7,18,.55) 52%,
      rgba(3,7,18,.30) 74%,
      rgba(3,7,18,.62) 100%);
  z-index: 0;
}

/* Variante «tinta»: lleva el azul marino del archivo al casi negro del manual.
   El manual es explícito sobre por qué —«every private-AI and security vendor
   ships some variant of cobalt, indigo or violet»—, así que el azul es
   exactamente el color del que la marca huye. Se baja la saturación en lugar de
   oscurecer a secas: oscurecer apagaría también el ámbar del cubo, que es lo
   único que hay que conservar. El radial le devuelve el calor al recorte. */
[data-gate-bg="ink"] .gate-aside::before {
  filter: saturate(.46) sepia(.42) brightness(.96) contrast(1.06);
}
[data-gate-bg="ink"] .gate-aside::after {
  background:
    linear-gradient(to top,
      rgba(3,7,18,.93) 0%,
      rgba(3,7,18,.74) 14%,
      rgba(3,7,18,.30) 26%,
      transparent 38%),
    radial-gradient(34rem 24rem at 74% 44%, rgba(245,158,11,.22), transparent 66%),
    linear-gradient(107deg,
      rgba(3,7,18,.94) 0%,
      rgba(3,7,18,.86) 28%,
      rgba(3,7,18,.48) 54%,
      rgba(3,7,18,.18) 76%,
      rgba(3,7,18,.46) 100%);
}

/* El titular pasa a dos líneas y cada una es un elemento. El span no es de
   bloque por sí solo dentro de un h2 flex-menos, así que se declara. */
.gate-claim { max-width: 14ch; }
.gate-claim > span { display: block; }

/* El isotipo de la tarjeta. Sin el disco de fondo del candado: la pieza ya
   tiene perímetro propio y meterla en un círculo la convertía en un avatar. */
.gate-badge-mark {
  width: 58px; height: 58px; border-radius: 0;
  background: none; color: var(--text);
}
.gate-badge-mark svg { width: 100%; height: 100%; }

/* ══════════════════════════════════════════════════════════════════════════════
   PUERTA A SANGRE · el canvas ES la pantalla
   ══════════════════════════════════════════════════════════════════════════════
   La lámina flotaba: un rectángulo de 76 rem con borde, radio y sombra, posado
   sobre el fondo de la aplicación. Eso convertía la primera pantalla del
   producto en una tarjeta dentro de otra pantalla, y leía como plantilla.

   Ahora no hay contenedor. El canvas del manual ocupa el viewport, la imagen
   sangra por el borde izquierdo y el formulario se apoya directamente sobre las
   capas. Las cinco del manual siguen ahí y no se duplican: canvas, rejilla al
   3 %, blob que respira, superficie y ruido al 1,5 %.

   LA PUERTA SE COMPROMETE AL OSCURO. DESIGN.md reserva claro/oscuro/sistema para
   consola y administración porque son superficies de jornada y «quien las usa
   toda la jornada elige». Una puerta no es una jornada: se cruza en quince
   segundos. El mismo razonamiento que compromete la puerta pública al canvas
   oscuro la compromete a ella. El conmutador de tema sigue dentro, donde actúa.
   ════════════════════════════════════════════════════════════════════════════ */

.gate {
  padding: 0;
  place-items: stretch;
  background: #030712;
  color-scheme: dark;

  /* Paleta comprometida. Se declara como tokens sobre la puerta y no se repinta
     regla a regla: así todo lo que cuelga de aquí —campos, botones, pie— hereda
     lo correcto sin que nadie tenga que acordarse. #B45309 queda fuera a
     propósito; el manual lo prohíbe expresamente sobre el canvas oscuro. */
  --bg:            #030712;
  --surface:       #0B1220;
  --text:          #F9FAFB;
  --text-2:        #D1D5DB;
  --text-muted:    #9CA3AF;
  --accent:        #F59E0B;
  --border:        rgba(255,255,255,.08);
}

/* Desmontaje de la lámina. Se listan los tres selectores que le ponían vidrio
   por tema porque igualan especificidad con los originales; al ir después en el
   archivo, ganan por orden de aparición. */
:root[data-theme="dark"] .gate-shell,
:root:not([data-theme="light"]) .gate-shell,
:root[data-theme="light"] .gate-shell,
.gate-shell {
  width: 100%; max-width: none; min-height: 100%;
  margin: 0; padding: 0;
  border: 0; border-radius: 0; box-shadow: none;
  background: none; backdrop-filter: none;
  display: flex; flex-direction: column;
  overflow: visible;
}

/* Asimétrico, no partido por la mitad. Un 50/50 es la retícula por defecto y se
   lee como tal; el peso cae del lado de la marca, que es lo que esta pantalla
   tiene que decir primero. */
.gate-split {
  position: relative;
  flex: 1 1 auto; min-height: 0;
  grid-template-columns: minmax(0, 1.12fr) minmax(0, 1fr);
}

/* ── Panel de marca ─────────────────────────────────────────────────────────── */

.gate-aside {
  padding: clamp(2rem, 4.4vw, 4.75rem);
  gap: clamp(.9rem, 1.8vw, 1.6rem);
  justify-content: center;
  border-right: 0;
}

/* Sin la ilustración que antes ocupaba el hueco, las garantías necesitan quien
   las empuje al pie. auto en el margen superior hace ese trabajo sin fijar
   alturas, que es lo que se rompía entre 900 y 1400 px de ventana. */
.gate-points { margin-top: auto; }

.gate-claim {
  max-width: 13ch;
  font-size: clamp(2.4rem, 4.4vw, 3.9rem);
  line-height: 1.03; letter-spacing: -.038em;
}
.gate-sell { max-width: 42ch; font-size: clamp(.95rem, 1.1vw, 1.05rem); }

/* ── Panel de formulario ────────────────────────────────────────────────────── */

.gate-main {
  position: relative;
  /* El blob que exige el manual, detras de donde se escribe. Sin el, la mitad
     derecha es un rectangulo casi negro y plano, y el manual lo dice con estas
     palabras: «el vidrio sobre oscuro plano se ve muerto». No es decoracion,
     es el mecanismo de profundidad de la marca. */
  background: radial-gradient(46rem 34rem at 58% 44%,
    rgba(245,158,11,.075), transparent 68%);
  padding: clamp(2rem, 4.4vw, 4.75rem);
  justify-content: center;
}

/* El filete superior en degradado es firma de marca y DESIGN.md pide
   conservarlo. La tarjeta que lo llevaba ya no existe, así que se muda a la
   costura entre los dos paneles, que es la única línea que queda en la
   pantalla. Un píxel: filete, nunca línea. */
.gate-main::before {
  content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 1px;
  background: linear-gradient(180deg,
    transparent 0%, rgba(245,158,11,.50) 24%,
    rgba(245,158,11,.50) 76%, transparent 100%);
  pointer-events: none;
}

/* El idioma se va a la esquina. En la lámina compartía columna con el
   formulario y lo empujaba hacia abajo; a sangre, esa columna mide media
   pantalla y el mando quedaba flotando en mitad de la nada. */
.gate-bar {
  position: absolute; margin: 0;
  top: clamp(1.25rem, 2.4vw, 2.25rem);
  right: clamp(1.25rem, 2.4vw, 2.25rem);
}

/* Deja de ser tarjeta. Sin fondo, sin borde, sin sombra y sin relleno: el
   formulario se apoya en el canvas. Alineado a la izquierda, porque una pila
   centrada dentro de un panel de media pantalla vuelve a leer como plantilla. */
.gate-card {
  width: 100%; max-width: 25.5rem; margin: 0; padding: 0;
  background: none; border: 0; box-shadow: none;
  gap: var(--s5);
}

/* Deja de ser superficie. Los tres selectores repiten los que le daban vidrio
   por tema: misma especificidad, y ganan porque van despues. Sin esto quedaba
   un rectangulo mas claro flotando sobre el canvas, que es justo el contenedor
   que sobraba. */
:root[data-theme="dark"] .gate-card,
:root:not([data-theme="light"]) .gate-card,
:root[data-theme="light"] .gate-card,
.gate-card {
  background: none; backdrop-filter: none; border: 0; box-shadow: none;
}
.gate-card::before { display: none; }

.gate-card h1 {
  text-align: left;
  font-size: clamp(1.9rem, 2.9vw, 2.5rem);
  letter-spacing: -.032em; line-height: 1.06;
}
.gate-sub { text-align: left; max-width: 32ch; }
.gate-foot { text-align: left; max-width: 34ch; }

/* Eyebrow del manual, con sus medidas: filete de 48 px, hueco de 12, mono 500 a
   0.15em en versales. Sustituye al isotipo que coronaba la tarjeta: la marca ya
   está enorme en el panel de al lado, y repetirla encima del titular era la
   pila icono-título-subtítulo que hay que evitar. */
.gate-eyebrow {
  margin: 0; display: flex; align-items: center; gap: 12px;
  font-family: var(--mono); font-size: .75rem; font-weight: 500;
  letter-spacing: .15em; text-transform: uppercase; color: var(--accent);
}
.gate-eyebrow::before {
  content: ""; flex: none; width: 48px; height: 1px; background: currentColor;
}

/* ── Campos y acción sobre oscuro ───────────────────────────────────────────── */

.gate .gate-field input {
  background: rgba(255,255,255,.04);
  border-color: rgba(255,255,255,.10);
  color: var(--text);
}
.gate .gate-field input::placeholder { color: #6B7280; }
.gate .gate-field input:focus {
  border-color: rgba(245,158,11,.55);
  box-shadow: 0 0 0 3px rgba(245,158,11,.13);
  background: rgba(255,255,255,.06);
}
.gate .field > label { color: var(--text-2); }

/* Ámbar pleno y texto oscuro encima. El acento de tema claro es #B45309 y el
   manual lo prohíbe sobre este canvas; además, texto blanco sobre ámbar no
   alcanza contraste y texto oscuro sí. */
.gate .btn.primary {
  background: #F59E0B; border-color: #F59E0B; color: #030712;
  font-weight: 650;
}
.gate .btn.primary:hover { background: #FCD34D; border-color: #FCD34D; }
.gate .btn.primary:focus-visible { outline-color: #FCD34D; }

/* ── Pie ────────────────────────────────────────────────────────────────────── */

.gate-footbar {
  background: none;
  border-top: 1px solid rgba(255,255,255,.07);
  padding-inline: clamp(1.5rem, 4.4vw, 4.75rem);
}

/* ── Movimiento ─────────────────────────────────────────────────────────────── */

/* El blob se muda al panel del formulario. Estaba arriba a la izquierda, donde
   la imagen lo tapa entero; el manual lo pide DETRAS de la superficie donde se
   escribe, porque «el vidrio sobre oscuro plano se ve muerto». Respira con
   gate-breathe, que ya existe: 9 s, la cifra del manual. */
.gate::after {
  top: -16vw; right: -9vw; left: auto;
  width: 58vw; height: 58vw;
}

/* La entrada deja de mover la lamina. A sangre la lamina ES la pantalla, y
   deslizarla 16 px arranca la sesion con un salto de todo el viewport. Entra
   solo el bloque que hay que leer. */
@media (prefers-reduced-motion: no-preference) {
  .gate-shell { animation: none; }
  .gate-card  { animation: gate-in .7s cubic-bezier(.16,1,.3,1) both; }
}

/* ── Estrecho ───────────────────────────────────────────────────────────────── */

@media (max-width: 860px) {
  .gate-split { grid-template-columns: 1fr; }
  .gate-aside { padding: clamp(1.5rem, 6vw, 2.5rem); min-height: 42vh; }
  .gate-main  { padding: clamp(1.75rem, 6vw, 2.75rem); }
  .gate-main::before {
    left: 0; right: 0; top: 0; bottom: auto; width: auto; height: 1px;
    background: linear-gradient(90deg,
      transparent 0%, rgba(245,158,11,.50) 24%,
      rgba(245,158,11,.50) 76%, transparent 100%);
  }
  .gate-bar { position: static; justify-content: flex-start; margin-bottom: var(--s5); }
  .gate-card { max-width: none; }
}

/* ══════════════════════════════════════════════════════════════════════════════
   PUERTA · LUZ, PROFUNDIDAD Y COREOGRAFÍA
   ══════════════════════════════════════════════════════════════════════════════
   Todo lo de aquí es mejora progresiva. El estado final se pinta sin JavaScript
   y sin animación: si gate-fx.js no carga, la puerta está completa y usable. Lo
   que este bloque añade es lo que separa una pantalla correcta de una que se
   recuerda.

   Se usa @property y no variables normales por un motivo concreto: una custom
   property sin tipo declarado es una cadena de texto para el navegador, y una
   cadena no se interpola. Sin esto, animar un ángulo de conic-gradient salta de
   0 a 360 en un fotograma en vez de girar.

   El color va en oklch. Es perceptualmente uniforme, así que bajar luminosidad
   no vira el tono: el ámbar de marca sigue siendo el mismo ámbar en todas sus
   intensidades, que es justo lo que un manual de tres colores necesita.
   ════════════════════════════════════════════════════════════════════════════ */

@property --gate-giro {
  syntax: "<angle>";
  inherits: false;
  initial-value: 0deg;
}

.gate {
  /* Reposo del puntero. gate-fx.js los reescribe; si no carga, la luz se queda
     en un sitio razonable en vez de en la esquina 0,0. */
  --mx: 50%;
  --my: 42%;
  --px: 0;
  --py: 0;
}

/* ── Aurora y luz de puntero ─────────────────────────────────────────────────
   Tres capas sobre el mismo panel, de atrás hacia delante: la aurora fija que
   da cuerpo al canvas, y la luz que sigue al cursor. La segunda existe porque
   una superficie oscura sin respuesta al gesto se siente impresa; con ella, el
   panel se lee como un material que refleja. */
.gate-main {
  background:
    radial-gradient(26rem 26rem at var(--mx) var(--my),
      oklch(78% .155 70 / .085), transparent 62%),
    radial-gradient(46rem 34rem at 58% 44%,
      oklch(78% .155 70 / .075), transparent 68%),
    radial-gradient(30rem 30rem at 92% 88%,
      oklch(62% .13 55 / .055), transparent 70%);
  transition: background-position .2s linear;
}

/* ── Paralaje de la imagen ───────────────────────────────────────────────────
   La imagen se mueve al contrario que el cursor, que es lo que el ojo espera de
   algo que está detrás del cristal: acompañándolo parecería pegada a la
   pantalla. El scale del 1.045 existe para que el desplazamiento no descubra el
   borde del recorte. */
[data-fx="on"] .gate-aside::before {
  transform:
    translate3d(calc(var(--px) * -9px), calc(var(--py) * -7px), 0)
    scale(1.045);
  transition: transform .45s cubic-bezier(.16, 1, .3, 1);
  will-change: transform;
}

/* ── Anillo de foco ──────────────────────────────────────────────────────────
   Un cónico de un píxel que gira mientras el campo tiene el foco. Sustituye al
   halo estático: en una pantalla oscura, un borde que respira dice «aquí
   escribes» sin necesidad de subir el contraste de todo lo demás.

   La máscara recorta el relleno al marco: se pinta el degradado en toda la caja
   y se excluye el interior, que es la forma de tener un borde con degradado sin
   inventarse un segundo elemento. */
.gate .gate-field { border-radius: var(--r2); }
.gate .gate-field::after {
  content: ""; position: absolute; inset: 0; border-radius: inherit;
  padding: 1px;
  background: conic-gradient(from var(--gate-giro),
    transparent 0deg,
    oklch(80% .17 72 / .95) 38deg,
    transparent 104deg,
    transparent 196deg,
    oklch(80% .17 72 / .55) 236deg,
    transparent 302deg);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
          mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
          mask-composite: exclude;
  opacity: 0;
  transition: opacity .28s ease-out;
  pointer-events: none;
}
.gate .gate-field:focus-within::after { opacity: 1; }

@media (prefers-reduced-motion: no-preference) {
  .gate .gate-field:focus-within::after {
    animation: gate-giro 3.6s linear infinite;
  }
}
@keyframes gate-giro { to { --gate-giro: 360deg; } }

/* Con el anillo puesto, el halo cuadrado de antes sobra: dos indicadores de
   foco sobre el mismo campo compiten y ninguno manda. */
.gate .gate-field:focus-within input { box-shadow: none; }

/* ── Coreografía de entrada ──────────────────────────────────────────────────
   Orden de lectura, no todo a la vez. El retardo lo pone gate-fx.js en --entra,
   pieza por pieza, para que añadir un elemento al panel no obligue a inventarle
   una regla con su propio delay.

   La salida es exponencial y sin rebote: el manual descarta el sobreimpulso, y
   con razón. Un formulario de acceso que da un botecito al aparecer se lee como
   una app de consumo, no como la puerta de la infraestructura de un banco. */
@media (prefers-reduced-motion: no-preference) {
  [data-fx="on"][data-entrada="lista"] :is(
    .gate-logo, .gate-rule, .gate-claim > span, .gate-claim > em,
    .gate-sell, .gate-points li,
    .gate-eyebrow, .gate-card h1, .gate-sub,
    .gate-card .field, .gate-card .gate-submit, .gate-foot
  ) {
    animation: gate-sube .78s cubic-bezier(.16, 1, .3, 1) both;
    animation-delay: var(--entra, 0ms);
  }

  @keyframes gate-sube {
    from { opacity: 0; transform: translate3d(0, 14px, 0); }
    to   { opacity: 1; transform: none; }
  }

  /* El filete se dibuja en lugar de aparecer. Es la pieza más pequeña de la
     pantalla y la que marca dónde empieza el mensaje; verla trazarse cuenta esa
     jerarquía sin una sola palabra. */
  [data-fx="on"][data-entrada="lista"] .gate-rule {
    animation: gate-traza .9s cubic-bezier(.16, 1, .3, 1) both;
    animation-delay: var(--entra, 0ms);
    transform-origin: left center;
  }
  @keyframes gate-traza {
    from { opacity: 0; transform: scaleX(0); }
    to   { opacity: 1; transform: none; }
  }

  /* El núcleo del isotipo late cada 3 s, que es la cifra que el manual reserva
     a los acentos. Es la única pieza de la pantalla que se mueve sola una vez
     terminada la entrada, y por eso puede permitírselo. */
  .gate-mark svg path:last-child {
    animation: gate-nucleo 3s ease-in-out 1.4s infinite;
    transform-origin: 52px 62px;
  }
  @keyframes gate-nucleo {
    0%, 100% { opacity: 1;   transform: scale(1); }
    50%      { opacity: .78; transform: scale(.93); }
  }
}

/* ── Transición entre formularios ────────────────────────────────────────────
   El paso de acceso a cambio obligatorio de contraseña lo funde la API de
   transiciones de vista, que gate-fx.js invoca cuando existe. Aquí solo se le
   dice cuánto y con qué curva. */
@media (prefers-reduced-motion: no-preference) {
  ::view-transition-old(root),
  ::view-transition-new(root) {
    animation-duration: .42s;
    animation-timing-function: cubic-bezier(.16, 1, .3, 1);
  }
}

/* ── El botón ────────────────────────────────────────────────────────────────
   Ámbar pleno, y la flecha avanza al pasar por encima. Un solo gesto, corto,
   que dice hacia dónde lleva el botón. */
.gate .btn.primary {
  transition: background-color .2s ease-out, border-color .2s ease-out,
              transform .2s cubic-bezier(.16, 1, .3, 1);
}
.gate .btn.primary svg {
  transition: transform .28s cubic-bezier(.16, 1, .3, 1);
}
@media (prefers-reduced-motion: no-preference) {
  .gate .btn.primary:hover svg { transform: translateX(3px); }
}
.gate .btn.primary:active { transform: translateY(1px); }


/* ── puerta: salida al sitio y olvido de contraseña ────────────────────────
   El enlace de olvido nace oculto y lo revela app.js cuando la instalación
   tiene correo. Cuando aparece va DEBAJO del botón de entrar y separado por un
   filete: es la segunda cosa que alguien busca en esta pantalla, y ponerlo
   antes competiría con la primera. */

.gate-bar { display: flex; align-items: center; gap: 12px; }
.gate-bar #gateLangSlot { margin-left: auto; }

.gate-back {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 6px 10px; border-radius: 8px;
  color: var(--muted, #6B7280); text-decoration: none;
  font-size: 12.5px; font-weight: 500;
  transition: color .2s ease, background-color .2s ease;
}
.gate-back svg { width: 14px; height: 14px; }
.gate-back:hover { color: var(--accent, #F59E0B); background: rgba(245,158,11,.08); }

#forgotRow {
  margin-top: 14px; padding-top: 14px;
  border-top: 1px solid var(--hairline, rgba(127,127,127,.16));
  text-align: center;
}
#forgotRow .linkish { font-size: 13.5px; font-weight: 600; }
