/* ============================================================
   MOTION — transitions.dev en el producto
   ------------------------------------------------------------
   Lo cargan todas las pantallas del producto (y las publicas) a
   traves de templates/core/_motion.html, justo despues de
   base.css. Contiene:

     1. Las variables semanticas de cada ficha usada, mapeadas a
        la escala de base.css (con fallback literal para las
        paginas que no cargan base.css).
     2. Los snippets de las fichas, pegados tal cual (selectores
        t-*, will-change y guard de reduced-motion incluidos).
     3. Utilidades Numio (nu-*): spinner unico, stagger por
        animacion, barras que crecen, pildora de tabs, etc.
     4. Un bloque global de prefers-reduced-motion.

   Lo que se usa desde JS lo orquesta static/js/motion.js
   (window.NumioMotion). No hay gettext aqui ni alli.

   Las fichas 33+ (tarjeta interactiva, orb, gooey, confirmar en
   sitio, carpeta, tasklist, campana) son reimplementaciones en CSS/JS
   vanilla de componentes de rareui.com (licencia libre, agradecen
   atribucion) y de las "interactive cards" de @kaolti.
   ============================================================ */

:root {
  /* ---- Escala (la misma tabla que base.css) ----
     base.css es la fuente para el producto; se repite aqui, con los mismos
     valores, por las paginas que no cargan base.css (login, registro,
     recuperar contrasena, onboarding de asesoria). Si cambia una, cambia
     la otra. */
  --duration-stagger: 40ms;
  --duration-micro: 80ms;
  --duration-quick: 150ms;
  --duration-fast: 250ms;
  --duration-medium: 350ms;
  --duration-slow: 400ms;
  --duration-very-slow: 500ms;
  --ease-smooth-out: cubic-bezier(0.22, 1, 0.36, 1);
  --ease-in-out: ease-in-out;
  --ease-out: ease-out;
  --ease-linear: linear;
  --ease-bounce: cubic-bezier(0.34, 1.36, 0.64, 1);
  --ease-bounce-strong: cubic-bezier(0.34, 3.85, 0.64, 1);
  --distance-micro: 4px; --distance-small: 6px; --distance-base: 8px; --distance-medium: 12px; --distance-large: 30px;
  --scale-large: 0.96; --scale-medium: 0.97; --scale-small: 0.98; --scale-tiny: 0.99;
  --blur-small: 2px; --blur-medium: 3px; --blur-large: 8px;

  /* 01 Card resize (ancho/alto de un contenedor al cambiar de estado) */
  --resize-dur:  var(--duration-fast, 250ms);
  --resize-ease: var(--ease-smooth-out, cubic-bezier(0.22, 1, 0.36, 1));
  /* 02 Number pop-in (cada digito re-entra con slide + blur) */
  --digit-dur:      var(--duration-very-slow, 500ms);
  --digit-distance: var(--distance-base, 8px);
  --digit-stagger:  70ms;
  --digit-blur:     var(--blur-small, 2px);
  --digit-ease:     cubic-bezier(0.34, 1.45, 0.64, 1);
  --digit-dir-x: 0;
  --digit-dir-y: 1;
  /* 03 Notification badge */
  --badge-slide-dur:      var(--duration-fast, 250ms);
  --badge-pop-dur:        var(--duration-very-slow, 500ms);
  --badge-pop-close-dur:  var(--duration-quick, 150ms);
  --badge-fade-dur:       var(--duration-slow, 400ms);
  --badge-fade-close-dur: var(--duration-quick, 150ms);
  --badge-blur:      var(--blur-small, 2px);
  --badge-offset-x: -8.2px;
  --badge-offset-y: 12.4px;
  --badge-slide-ease: var(--ease-smooth-out, cubic-bezier(0.22, 1, 0.36, 1));
  --badge-pop-ease:   var(--ease-bounce, cubic-bezier(0.34, 1.36, 0.64, 1));
  --badge-close-ease: cubic-bezier(0.4, 0, 0.2, 1);
  /* 04 Text states swap */
  --text-swap-dur:         var(--duration-quick, 150ms);
  --text-swap-translate-y: var(--distance-micro, 4px);
  --text-swap-blur:        var(--blur-small, 2px);
  --text-swap-ease:        var(--ease-in-out, ease-in-out);
  /* 05 Menu dropdown */
  --dropdown-open-dur:      var(--duration-fast, 250ms);
  --dropdown-close-dur:     var(--duration-quick, 150ms);
  --dropdown-pre-scale:     var(--scale-medium, 0.97);
  --dropdown-closing-scale: var(--scale-tiny, 0.99);
  --dropdown-ease:          var(--ease-smooth-out, cubic-bezier(0.22, 1, 0.36, 1));
  /* 06 Modal: entra asentandose (sube 12px, escala .97 -> 1, de borroso a
     nitido) y sale rapido; la superficie la pone css/overlays.css. */
  --modal-open-dur:    280ms;
  --modal-close-dur:   var(--duration-quick, 150ms);
  --modal-y:           var(--distance-medium, 12px);
  --modal-scale:       var(--scale-medium, 0.97);
  --modal-scale-close: var(--scale-small, 0.98);
  --modal-blur:        4px;
  --modal-ease:        var(--ease-smooth-out, cubic-bezier(0.22, 1, 0.36, 1));
  /* 06b Cajon lateral (NumioMotion.drawer): entra desde 24px con opacidad y
     sale rapido; la superficie la pone css/overlays.css. */
  --drawer-open-dur:   var(--duration-fast, 250ms);
  --drawer-close-dur:  var(--duration-quick, 150ms);
  --drawer-x:          24px;
  /* 07 Panel reveal (la ficha trae 100px de recorrido: aqui son paneles cortos) */
  --panel-open-dur:    var(--duration-slow, 400ms);
  --panel-close-dur:   var(--duration-medium, 350ms);
  --panel-translate-y: var(--distance-medium, 12px);
  --panel-blur:        var(--blur-small, 2px);
  --panel-ease:        var(--ease-smooth-out, cubic-bezier(0.22, 1, 0.36, 1));
  /* 08 Page side-by-side (slides de los asistentes) */
  --page-slide-dur:      var(--duration-fast, 250ms);
  --page-fade-dur:       var(--duration-fast, 250ms);
  --page-slide-distance: var(--distance-base, 8px);
  --page-blur:           var(--blur-medium, 3px);
  --page-slide-ease:     var(--ease-smooth-out, cubic-bezier(0.22, 1, 0.36, 1));
  --page-fade-ease:      var(--ease-smooth-out, cubic-bezier(0.22, 1, 0.36, 1));
  /* 09 Icon swap */
  --icon-swap-dur:         var(--duration-fast, 250ms);
  --icon-swap-blur:        var(--blur-small, 2px);
  --icon-swap-start-scale: 0.25;
  --icon-swap-ease:        var(--ease-in-out, ease-in-out);
  /* 10 Success check */
  --check-opacity-dur: var(--duration-very-slow, 500ms);
  --check-rotate-dur:  var(--duration-very-slow, 500ms);
  --check-rotate-from: 80deg;
  --check-bob-dur:     var(--duration-very-slow, 500ms);
  --check-y-amount:    var(--distance-medium, 12px);   /* la ficha trae 40px: es un icono, no un panel */
  --check-blur-dur:    var(--duration-very-slow, 500ms);
  --check-blur-from:   var(--blur-large, 8px);
  --check-path-dur:    var(--duration-very-slow, 500ms);
  --check-path-delay:  var(--duration-micro, 80ms);
  --check-ease-out:     var(--ease-smooth-out, cubic-bezier(0.22, 1, 0.36, 1));
  --check-ease-opacity: var(--ease-smooth-out, cubic-bezier(0.22, 1, 0.36, 1));
  --check-ease-rotate:  var(--ease-smooth-out, cubic-bezier(0.22, 1, 0.36, 1));
  --check-ease-bob:     cubic-bezier(0.34, 1.35, 0.64, 1);
  --check-ease-path:    var(--ease-smooth-out, cubic-bezier(0.22, 1, 0.36, 1));
  /* 12 Error state shake */
  --shake-distance:  var(--distance-small, 6px);
  --shake-overshoot: var(--distance-micro, 4px);
  --shake-dur-a:     var(--duration-micro, 80ms);
  --shake-dur-b:     60ms;
  --shake-ease:      var(--ease-smooth-out, cubic-bezier(0.22, 1, 0.36, 1));
  --revert-hold:     3000ms;
  --revert-dur:      var(--duration-medium, 350ms);
  /* 14 Skeleton loader and reveal */
  --pulse-dur:   1000ms;
  --pulse-count: 1;
  --pulse-min:   0.5;
  --reveal-dur:  var(--duration-slow, 400ms);
  --reveal-blur: var(--blur-small, 2px);
  --reveal-ease: var(--ease-in-out, ease-in-out);
  /* 15 Shimmer text (etiqueta "pensando" del chat, sin spinner) */
  --shimmer-dur:       2000ms;
  --shimmer-base:      var(--muted, #6B7280);
  --shimmer-highlight: var(--ink, #0F0B1E);
  --shimmer-band:      400%;
  --shimmer-ease:      linear;
  /* 16 Tabs sliding (solo el movimiento; la cosmetica la pone cada barra) */
  --tabs-dur:  var(--duration-fast, 250ms);
  --tabs-ease: var(--ease-smooth-out, cubic-bezier(0.22, 1, 0.36, 1));
  /* 17 Tooltip */
  --tt-in-dur:  var(--duration-quick, 150ms);
  --tt-out-dur: 50ms;
  --tt-scale:   var(--scale-small, 0.98);
  --tt-delay:   var(--duration-micro, 80ms);
  --tt-in-ease:  var(--ease-out, ease-out);
  --tt-out-ease: var(--ease-out, ease-out);
  --tt-bg: var(--ink, #0F0B1E);
  --tt-fg: #ffffff;
  /* 18 Texts reveal / stagger */
  --stagger-dur:      var(--duration-very-slow, 500ms);
  --stagger-distance: var(--distance-medium, 12px);
  --stagger-stagger:  var(--duration-stagger, 40ms);
  --stagger-blur:     var(--blur-medium, 3px);
  --stagger-ease:     var(--ease-smooth-out, cubic-bezier(0.22, 1, 0.36, 1));
  /* 21 Accordion */
  --acc-expand:   var(--duration-fast, 250ms);
  --acc-collapse: var(--duration-fast, 250ms);
  --acc-chevron:  var(--duration-fast, 250ms);
  --acc-ease:     var(--ease-smooth-out, cubic-bezier(0.22, 1, 0.36, 1));
  /* 22 Toast */
  --toast-open:     var(--duration-medium, 350ms);
  --toast-close:    var(--duration-fast, 250ms);
  --toast-distance: 16px;
  --toast-blur:     var(--blur-small, 2px);
  --toast-scale:    var(--scale-medium, 0.97);
  --toast-ease:     var(--ease-smooth-out, cubic-bezier(0.22, 1, 0.36, 1));
  /* 26 Spinning counter */
  --reel-dur:       1400ms;
  --reel-cell:      1.2em;
  --reel-spin-blur: var(--blur-medium, 3px);
  --reel-stagger:   90ms;
  --reel-ease:      cubic-bezier(0.16, 1, 0.3, 1);
  /* 27 Toggle (los switches del producto viajan 16px) */
  --toggle-dur:    var(--duration-medium, 350ms);
  --toggle-travel: 16px;
  --toggle-ov1:    1px;
  --toggle-ov2:    0px;
  --toggle-track:  var(--duration-quick, 150ms);
  --toggle-ease:   cubic-bezier(0.34, 1.35, 0.64, 1);
  /* 28 Thinking states (solo esperas que NO son LLM: etapas de un proceso) */
  --think-hold:      2000ms;
  --think-swap:      var(--duration-quick, 150ms);
  --think-gap:       50ms;
  --think-distance:  var(--distance-base, 8px);
  --think-blur:      var(--blur-small, 2px);
  --think-shimmer:   2000ms;
  --think-base:      var(--muted, #6B7280);
  --think-highlight: var(--ink, #0F0B1E);
  --think-ease:      var(--ease-in-out, ease-in-out);
  /* Chart.js (NumioMotion.chart): primer pintado y cambio de datos. El cambio
     de datos (periodo, filtros) va mas largo y con entrada-salida suave, para
     que las series se vean ir de un valor al otro en vez de saltar. */
  --chart-draw-dur:   var(--duration-very-slow, 500ms);
  --chart-update-dur: 700ms;
  /* 30 Streaming text (la respuesta del chat se resuelve palabra a palabra) */
  --stream-gap:  60ms;
  --stream-fade: var(--duration-medium, 350ms);
  --stream-blur: 1px;
  --stream-ease: var(--ease-smooth-out, cubic-bezier(0.22, 1, 0.36, 1));
  /* 31 Matrix dot loader */
  --matrix-cycle:  1200ms;
  --matrix-base:   var(--line, #E8E3F8);
  --matrix-active: var(--brand, #6B5BFF);
  --matrix-ease:   var(--ease-in-out, ease-in-out);
  /* 32 Banner stacking (variante "lite": banners fijos arriba, entran desde 12px) */
  --stack-open:  var(--duration-medium, 350ms);
  --stack-close: var(--duration-fast, 250ms);
  --stack-rise:  var(--distance-medium, 12px);
  --stack-blur:  var(--blur-small, 2px);
  --stack-scale: var(--scale-medium, 0.97);
  --stack-ease:  var(--ease-smooth-out, cubic-bezier(0.22, 1, 0.36, 1));
  /* 33 Tarjeta interactiva (tilt + foco + esquinas visor + parallax) */
  --tilt-return:     var(--duration-medium, 350ms);
  --tilt-vf-dur:     var(--duration-fast, 250ms);
  --tilt-vf-len:     14px;
  --tilt-vf-inset:   8px;
  --tilt-vf-thick:   1.5px;
  --tilt-vf-color:   rgba(255, 255, 255, .75);
  --tilt-glow-size:  240px;
  --tilt-glow-color: rgba(255, 255, 255, .22);
  --tilt-parallax:   10px;
  /* 34 Confirmar en sitio (papelera que abre la tapa + panel si/no) */
  --confirm-dur:    var(--duration-fast, 250ms);
  --confirm-ease:   var(--ease-smooth-out, cubic-bezier(0.22, 1, 0.36, 1));
  --confirm-yes-bg: var(--color-negative, #ef4444);
  --confirm-no-bg:  var(--surface-soft, #f1f5f9);
  --confirm-no-fg:  var(--muted, #64748b);
  /* 35 Carpeta animada (documentos: estado vacio y zona de arrastre) */
  --folder-size: 72px;
  --folder-dur:  var(--duration-medium, 350ms);
  --folder-ease: var(--ease-smooth-out, cubic-bezier(0.22, 1, 0.36, 1));
  --folder-c:    var(--brand, #6B5BFF);
  --folder-c2:   var(--brand-400, #8A7BFF);
  /* 36 Tabs gooey (la pildora se despega de la barra con union liquida) */
  --goo-blur: 4;      /* stdDeviation del filtro; lo lee el JS al inyectarlo */
  --goo-lag:  1.6;    /* el blob rezagado tarda 1.6x la pildora: es la "estela" */
  --goo-hop:  3px;    /* salto del blob lider al moverse */
  /* 37 Lista de tareas (check dibujado, tachado y la fila baja al final) */
  --task-strike: var(--duration-medium, 350ms);
  --task-hold:   250ms;
  --task-move:   var(--duration-medium, 350ms);
  --task-accent: var(--brand, #6B5BFF);
  /* 38 Orb de puntos (chat: voz, lienzo vacio, pensando) */
  --orb-idle:      var(--muted, #8b8aa3);
  --orb-listening: #10b981;
  --orb-thinking:  var(--brand, #6B5BFF);
  --orb-speaking:  var(--brand, #6B5BFF);
  --orb-error:     #dc2626;
  /* 39 Campana con contador */
  --bell-dur: 900ms;
}

/* Lo que va con [hidden] tiene que desaparecer de verdad aunque la
   pantalla le haya dado display:flex. Solo a los ganchos de este fichero. */
.t-dropdown[hidden],
.t-modal-root[hidden],
.t-drawer-root[hidden],
.t-panel-slide[hidden],
.nu-banner[hidden] { display: none !important; }

/* Bloqueo de scroll de NumioMotion.lock(): contado, una clase, sin
   `body.style.overflow` a mano. El padding compensa la barra de scroll que
   desaparece para que la pagina no salte. */
body.has-modal { overflow: hidden; padding-right: var(--nu-scrollbar, 0px); }

/* ============================================================
   01 Card resize
   ============================================================ */
.t-resize {
  transition:
    width  var(--resize-dur) var(--resize-ease),
    height var(--resize-dur) var(--resize-ease);
  will-change: width, height;
}

/* ============================================================
   02 Number pop-in — motion.js envuelve la cifra en un
   .t-digit-group dentro del elemento, asi el elemento conserva
   su display.
   ============================================================ */
@keyframes t-digit-pop-in {
  0%   {
    transform: translate(
      calc(var(--digit-distance) * var(--digit-dir-x)),
      calc(var(--digit-distance) * var(--digit-dir-y))
    );
    opacity: 0;
    filter: blur(var(--digit-blur));
  }
  100% { transform: translate(0, 0); opacity: 1; filter: blur(0); }
}
.t-digit-group {
  display: inline-flex;
  align-items: baseline;
}
.t-digit {
  display: inline-block;
  white-space: pre;
  will-change: transform, opacity, filter;
}
.t-digit-group.is-animating .t-digit {
  animation: t-digit-pop-in var(--digit-dur) var(--digit-ease) both;
}
.t-digit-group.is-animating .t-digit[data-stagger="1"] {
  animation-delay: var(--digit-stagger);
}
.t-digit-group.is-animating .t-digit[data-stagger="2"] {
  animation-delay: calc(var(--digit-stagger) * 2);
}

/* ============================================================
   03 Notification badge
   ============================================================ */
@keyframes t-badge-slide-in {
  from { transform: translate(var(--badge-offset-x), var(--badge-offset-y)); }
  to   { transform: translate(0, 0); }
}
.t-badge {
  position: absolute;
  top: -6px;
  right: -8px;
  pointer-events: none;
  will-change: transform;
}
.t-badge.is-inline { position: static; pointer-events: auto; }
.t-badge[data-open="true"] {
  animation: t-badge-slide-in var(--badge-slide-dur) var(--badge-slide-ease);
}
.t-badge-dot {
  display: block;
  transform-origin: center;
  transform: scale(1);
  opacity: 1;
  filter: blur(0);
  transition:
    transform var(--badge-pop-dur)  var(--badge-pop-ease),
    opacity   var(--badge-fade-dur) var(--badge-pop-ease),
    filter    var(--badge-pop-dur)  var(--badge-pop-ease);
  will-change: transform, opacity, filter;
}
/* La segunda forma es la del globo sin hijo (NumioMotion.badge sobre un
   solo nodo: el propio globo es el punto). */
.t-badge[data-open="false"] .t-badge-dot,
.t-badge.t-badge-dot[data-open="false"] {
  transform: scale(0);
  opacity: 0;
  filter: blur(var(--badge-blur));
  transition:
    transform var(--badge-pop-close-dur)  var(--badge-close-ease),
    opacity   var(--badge-fade-close-dur) var(--badge-close-ease),
    filter    var(--badge-pop-close-dur)  var(--badge-close-ease);
}

/* ============================================================
   04 Text states swap — motion.js lo aplica a un span interior
   ============================================================ */
.t-text-swap {
  display: inline-block;
  transform: translateY(0);
  filter: blur(0);
  opacity: 1;
  transition:
    transform var(--text-swap-dur) var(--text-swap-ease),
    filter    var(--text-swap-dur) var(--text-swap-ease),
    opacity   var(--text-swap-dur) var(--text-swap-ease);
  will-change: transform, filter, opacity;
}
.t-text-swap.is-exit {
  transform: translateY(calc(var(--text-swap-translate-y) * -1));
  filter: blur(var(--text-swap-blur));
  opacity: 0;
}
.t-text-swap.is-enter-start {
  transform: translateY(var(--text-swap-translate-y));
  filter: blur(var(--text-swap-blur));
  opacity: 0;
  transition: none;
}
/* Variante de dos spans apilados (boton "Entrar" -> "Entrando..."). */
.nu-text-swap {
  display: inline-grid;
  justify-items: center;
}
.nu-text-swap > span {
  grid-area: 1 / 1;
  transition:
    opacity   var(--text-swap-dur) var(--text-swap-ease),
    transform var(--text-swap-dur) var(--text-swap-ease),
    filter    var(--text-swap-dur) var(--text-swap-ease);
  will-change: opacity, transform, filter;
}
.nu-text-swap > .is-exit {
  opacity: 0;
  transform: translateY(calc(var(--text-swap-translate-y) * -1));
  filter: blur(var(--text-swap-blur));
}
.nu-text-swap > .is-enter-start {
  opacity: 0;
  transform: translateY(var(--text-swap-translate-y));
  filter: blur(var(--text-swap-blur));
}

/* ============================================================
   05 Menu dropdown
   ============================================================ */
.t-dropdown {
  transform-origin: top left;
  transform: scale(var(--dropdown-pre-scale));
  opacity: 0;
  pointer-events: none;
  transition:
    transform var(--dropdown-open-dur) var(--dropdown-ease),
    opacity   var(--dropdown-open-dur) var(--dropdown-ease);
  will-change: transform, opacity;
}
.t-dropdown[data-origin="top-right"]     { transform-origin: top right; }
.t-dropdown[data-origin="top-center"]    { transform-origin: top center; }
.t-dropdown[data-origin="bottom-left"]   { transform-origin: bottom left; }
.t-dropdown[data-origin="bottom-center"] { transform-origin: bottom center; }
.t-dropdown[data-origin="bottom-right"]  { transform-origin: bottom right; }
.t-dropdown.is-open {
  transform: scale(1);
  opacity: 1;
  pointer-events: auto;
}
.t-dropdown.is-closing {
  transform: scale(var(--dropdown-closing-scale));
  opacity: 0;
  pointer-events: none;
  transition:
    transform var(--dropdown-close-dur) var(--dropdown-ease),
    opacity   var(--dropdown-close-dur) var(--dropdown-ease);
}

/* ============================================================
   06 Modal (+ fondo: la ficha no lo trae)
   ============================================================ */
.t-modal {
  transform-origin: center;
  transform: translateY(var(--modal-y)) scale(var(--modal-scale));
  opacity: 0;
  filter: blur(var(--modal-blur));
  pointer-events: none;
  transition:
    transform var(--modal-open-dur) var(--modal-ease),
    opacity   var(--modal-open-dur) var(--modal-ease),
    filter    var(--modal-open-dur) var(--modal-ease);
}
.t-modal.is-open {
  transform: none;               /* none, no scale(1): sin capa de composicion el texto queda nitido */
  opacity: 1;
  filter: none;                  /* idem: la capa del blur se suelta al asentarse */
  pointer-events: auto;
}
.t-modal:focus { outline: none; } /* el foco entra en el dialogo (tabindex=-1) sin dibujar un anillo */
.t-modal.is-closing {
  transform: translateY(6px) scale(var(--modal-scale-close));
  opacity: 0;
  filter: none;
  pointer-events: none;
  transition:
    transform var(--modal-close-dur) var(--modal-ease),
    opacity   var(--modal-close-dur) var(--modal-ease);
}
.t-modal-backdrop {
  opacity: 0;
  transition: opacity var(--duration-fast, 250ms) var(--modal-ease);
}
.t-modal-backdrop.is-open { opacity: 1; }
.t-modal-backdrop.is-closing {
  opacity: 0;
  transition: opacity var(--modal-close-dur) var(--modal-ease);
}

/* ============================================================
   07 Panel reveal (+ visibility retardada para que cerrado no
   reciba foco ni lo lea un lector de pantalla)
   ============================================================ */
.t-panel-slide {
  transform: translateY(var(--panel-translate-y));
  opacity: 0;
  filter: blur(var(--panel-blur));
  pointer-events: none;
  visibility: hidden;
  transition:
    transform  var(--panel-close-dur) var(--panel-ease),
    opacity    var(--panel-close-dur) var(--panel-ease),
    filter     var(--panel-close-dur) var(--panel-ease),
    visibility 0s linear var(--panel-close-dur);
  will-change: transform, opacity, filter;
}
.t-panel-slide[data-open="true"] {
  transform: translateY(0);
  opacity: 1;
  filter: blur(0);
  pointer-events: auto;
  visibility: visible;
  transition:
    transform  var(--panel-open-dur) var(--panel-ease),
    opacity    var(--panel-open-dur) var(--panel-ease),
    filter     var(--panel-open-dur) var(--panel-ease),
    visibility 0s;
}

/* ============================================================
   09 Icon swap
   ============================================================ */
.t-icon-swap {
  position: relative;
  display: inline-grid;
  place-items: center;
}
.t-icon-swap .t-icon {
  grid-area: 1 / 1;
  display: inline-flex;
  transition:
    opacity   var(--icon-swap-dur) var(--icon-swap-ease),
    filter    var(--icon-swap-dur) var(--icon-swap-ease),
    transform var(--icon-swap-dur) var(--icon-swap-ease);
  will-change: opacity, filter, transform;
}
.t-icon-swap[data-state="a"] .t-icon[data-icon="a"],
.t-icon-swap[data-state="b"] .t-icon[data-icon="b"] {
  opacity: 1;
  filter: blur(0);
  transform: scale(1);
}
.t-icon-swap[data-state="a"] .t-icon[data-icon="b"],
.t-icon-swap[data-state="b"] .t-icon[data-icon="a"] {
  opacity: 0;
  filter: blur(var(--icon-swap-blur));
  transform: scale(var(--icon-swap-start-scale));
}

/* ============================================================
   10 Success check — arranca con data-state="in" (sin JS) o
   con NumioMotion.success(el) para repetirlo
   ============================================================ */
.t-success-check {
  display: inline-block;
  transform-origin: center;
  opacity: 0;
  will-change: transform, opacity, filter;
}
.t-success-check svg { display: block; overflow: visible; }
.t-success-check svg path {
  stroke-dasharray: var(--success-len, 36);   /* getTotalLength() del trazo M12 25l8 8 16-17 = 34.7 */
  stroke-dashoffset: var(--success-len, 36);
}
.t-success-check[data-state="in"] {
  animation:
    t-check-fade   var(--check-opacity-dur) var(--check-ease-opacity) forwards,
    t-check-rotate var(--check-rotate-dur)  var(--check-ease-rotate)  forwards,
    t-check-blur   var(--check-blur-dur)    var(--check-ease-out)     forwards,
    t-check-bob    var(--check-bob-dur)     var(--check-ease-bob)     forwards;
}
.t-success-check[data-state="in"] svg path {
  animation: t-check-draw var(--check-path-dur) var(--check-ease-path) var(--check-path-delay, 0ms) forwards;
}
@keyframes t-check-fade   { from { opacity: 0; } to { opacity: 1; } }
@keyframes t-check-rotate { from { transform: rotate(var(--check-rotate-from)); } to { transform: rotate(0deg); } }
@keyframes t-check-blur   { from { filter: blur(var(--check-blur-from)); } to { filter: blur(0); } }
@keyframes t-check-bob    { from { translate: 0 var(--check-y-amount); } to { translate: 0 0; } }
@keyframes t-check-draw   { to { stroke-dashoffset: 0; } }

/* ============================================================
   12 Error state shake — .is-shaking sobre cualquier nodo (la
   ficha lo pone en .t-input; aqui se sacude el campo entero)
   ============================================================ */
.is-shaking {
  animation: t-input-shake calc(var(--shake-dur-a) * 2 + var(--shake-dur-b) * 2) linear;
}
@keyframes t-input-shake {
  0%      { transform: translateX(0);                                 animation-timing-function: var(--shake-ease); }
  28.57%  { transform: translateX(var(--shake-distance));             animation-timing-function: var(--shake-ease); }
  57.14%  { transform: translateX(calc(var(--shake-distance) * -1)); animation-timing-function: var(--shake-ease); }
  78.57%  { transform: translateX(var(--shake-overshoot));            animation-timing-function: var(--shake-ease); }
  100%    { transform: translateX(0); }
}

/* ============================================================
   14 Skeleton loader and reveal (+ variante en flujo: el
   contenido define la altura y el esqueleto flota encima)
   ============================================================ */
.t-skel { position: relative; }
.t-skel-skeleton,
.t-skel-content {
  position: absolute;
  inset: 0;
}
.t-skel.is-flow .t-skel-content { position: static; }
.t-skel-skeleton {
  z-index: 1;
  opacity: 1;
  filter: blur(0);
  transition:
    opacity var(--reveal-dur) var(--reveal-ease),
    filter  var(--reveal-dur) var(--reveal-ease);
}
.t-skel-content {
  z-index: 2;
  opacity: 0;
  filter: blur(var(--reveal-blur));
  transition:
    opacity var(--reveal-dur) var(--reveal-ease),
    filter  var(--reveal-dur) var(--reveal-ease);
}
.t-skel.is-revealed .t-skel-skeleton {
  opacity: 0;
  filter: blur(var(--reveal-blur));
  pointer-events: none;
}
.t-skel.is-revealed .t-skel-content {
  opacity: 1;
  filter: blur(0);
}
.t-skel.is-resetting .t-skel-skeleton,
.t-skel.is-resetting .t-skel-content {
  transition: none !important;
}
.t-skel-skeleton.is-pulsing > * {
  animation: t-skel-pulse var(--pulse-dur) ease-in-out var(--pulse-count);
}
@keyframes t-skel-pulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: var(--pulse-min); }
}
/* Barras de esqueleto genericas (el color lo puede afinar cada pantalla). */
.nu-skel-bar {
  display: block;
  height: 12px;
  border-radius: 6px;
  background: var(--line, #E8E3F8);
  margin: 8px 0;
}
/* "Skeleton-lite": el valor que ya esta pintado late mientras llega el nuevo. */
.nu-pulse { animation: t-skel-pulse var(--pulse-dur) ease-in-out infinite; }

/* ============================================================
   16 Tabs sliding — solo la pildora; la barra conserva su CSS
   (motion.js le pone has-pill y position:relative)
   ============================================================ */
.t-tabs-pill {
  position: absolute;
  top: 3px;
  left: 0;
  height: 30px;
  width: 0;
  border-radius: 48px;
  transform: translateX(0);
  transition:
    transform var(--tabs-dur) var(--tabs-ease),
    width     var(--tabs-dur) var(--tabs-ease);
  will-change: transform, width;
  z-index: 0;
  pointer-events: none;
}
.nu-tabs-pill {
  background: var(--surface-base, #fff);
  box-shadow: var(--shadow-sm, 0 1px 2px rgba(15, 11, 30, .05));
}
.has-pill > * { position: relative; z-index: 1; }
/* La regla anterior alcanza tambien a la pildora (misma especificidad, va
   despues): se le devuelve el absolute o la barra le reserva hueco. */
.has-pill > .t-tabs-pill { position: absolute; z-index: 0; }
/* Variante gooey: capa de fondo con filtro (pista + blob rezagado + blob
   lider). Va debajo de la pildora nitida y de los botones; el filtro solo
   toca esta capa, nunca el texto. La barra pasa a fondo transparente (el
   JS copia su fondo a la pista). */
.t-tabs-goo {
  position: absolute; inset: 0; z-index: 0; pointer-events: none;
  border-radius: inherit;
  filter: url(#nu-goo);
}
.has-pill > .t-tabs-goo { position: absolute; z-index: 0; }
.t-tabs-goo__track { position: absolute; inset: 0; border-radius: inherit; }
.t-tabs-goo__blob {
  position: absolute; left: 0;
  top: var(--y, 3px); height: var(--h, 30px); width: var(--w, 0);
  transform: translateX(var(--x, 0));
  border-radius: 48px;
  transition: transform var(--tabs-dur) var(--tabs-ease), width var(--tabs-dur) var(--tabs-ease), opacity var(--duration-quick, 150ms) var(--ease-out, ease-out);
}
.t-tabs-goo__blob--lag {
  transition-duration: calc(var(--tabs-dur) * var(--goo-lag)), calc(var(--tabs-dur) * var(--goo-lag)), var(--duration-quick, 150ms);
  transition-timing-function: cubic-bezier(.3, .7, .2, 1);
}
.t-tabs-goo.is-moving > .t-tabs-goo__blob:not(.t-tabs-goo__blob--lag) {
  animation: t-goo-hop calc(var(--tabs-dur) * 1.2) var(--ease-smooth-out, cubic-bezier(0.22, 1, 0.36, 1));
}
@keyframes t-goo-hop {
  0%, 100% { translate: 0 0; scale: 1 1; }
  40% { translate: 0 calc(var(--goo-hop) * -1); scale: 1.04 1.12; }
}
.t-tabs-goo.is-snap > * { transition: none !important; animation: none !important; }
.t-tabs-goo.is-empty > .t-tabs-goo__blob { opacity: 0; }
@media (forced-colors: active) { .t-tabs-goo { display: none; } }

/* Variante subrayado (tabs de texto): una linea de 2px al pie. */
.nu-tabs--underline > .nu-tabs-pill {
  top: auto !important;
  bottom: 0;
  height: 2px !important;
  border-radius: 1px;
  background: var(--brand, #6B5BFF);
  box-shadow: none;
}

/* ============================================================
   17 Tooltip
   ============================================================ */
.t-tt-wrap {
  position: relative;
  display: inline-block;
}
.t-tt {
  position: absolute;
  bottom: calc(100% + 8px);
  left: 50%;
  transform: translate(-50%, 0) scale(var(--tt-scale));
  transform-origin: 50% 100%;
  padding: 8px 12px;
  border-radius: 8px;
  background: var(--tt-bg);
  color: var(--tt-fg);
  white-space: nowrap;
  box-shadow:
    0 0 0 1px rgba(0, 0, 0, 0.06),
    0 2px 6px 0 rgba(0, 0, 0, 0.05),
    0 4px 42px 0 rgba(0, 0, 0, 0.06);
  opacity: 0;
  pointer-events: none;
  transition:
    opacity   var(--tt-out-dur) var(--tt-out-ease),
    transform var(--tt-out-dur) var(--tt-out-ease);
}
.t-tt-wrap:hover .t-tt,
.t-tt-trigger:focus-visible + .t-tt {
  opacity: 1;
  transform: translate(-50%, 0) scale(1);
  transition-duration: var(--tt-in-dur);
  transition-timing-function: var(--tt-in-ease);
  transition-delay: var(--tt-delay);
}

/* ============================================================
   18 Texts reveal (lineas de texto) + variante por item para
   listas regeneradas: ANIMACION con fill backwards, para que
   ni la duracion ni el desfase se cuelen luego en el hover de
   las tarjetas (una transicion dejaria residuo).
   ============================================================ */
.t-stagger-line {
  display: block;
  opacity: 0;
  transform: translateY(var(--stagger-distance));
  filter: blur(var(--stagger-blur));
  transition:
    opacity   var(--stagger-dur) var(--stagger-ease),
    transform var(--stagger-dur) var(--stagger-ease),
    filter    var(--stagger-dur) var(--stagger-ease);
  will-change: transform, opacity, filter;
}
.t-stagger-line--2 { transition-delay: var(--stagger-stagger); }
.t-stagger.is-shown .t-stagger-line {
  opacity: 1;
  transform: translateY(0);
  filter: blur(0);
}
.t-stagger.is-hiding .t-stagger-line {
  opacity: 0;
  transform: translateY(0);
  filter: blur(0);
  transition:
    opacity 200ms ease,
    transform 0s linear,
    filter 0s linear;
  transition-delay: 0s;
}
.t-stagger-item {
  animation: nu-stagger-in var(--stagger-dur) var(--stagger-ease) backwards;
  animation-delay: calc(var(--i, 0) * var(--stagger-stagger));
}
tr.t-stagger-item {
  animation-name: nu-row-in;
}
@keyframes nu-stagger-in {
  from { opacity: 0; transform: translateY(var(--distance-micro, 4px)); filter: blur(var(--blur-small, 2px)); }
}
@keyframes nu-row-in {
  from { opacity: 0; }
}
.is-leaving {
  opacity: 0 !important;
  transition: opacity var(--duration-quick, 150ms) var(--ease-out, ease-out);
}

/* ============================================================
   21 Accordion
   ============================================================ */
.t-acc-panel {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows var(--acc-collapse) var(--acc-ease);
}
.t-acc[data-open="true"] .t-acc-panel {
  grid-template-rows: 1fr;
  transition: grid-template-rows var(--acc-expand) var(--acc-ease);
}
.t-acc-panel-inner {
  overflow: hidden;
  min-height: 0;
  opacity: 0;
  filter: blur(2px);
  transition:
    opacity var(--acc-collapse) var(--acc-ease),
    filter var(--acc-collapse) var(--acc-ease);
}
.t-acc[data-open="true"] .t-acc-panel-inner {
  opacity: 1;
  filter: blur(0);
  transition:
    opacity var(--acc-expand) var(--acc-ease),
    filter var(--acc-expand) var(--acc-ease);
}
.t-acc-chevron {
  display: inline-flex;
  transform: scaleY(1);
  transform-origin: center;
  transition: transform var(--acc-chevron) var(--acc-ease);
}
.t-acc-chevron path { vector-effect: non-scaling-stroke; }
.t-acc[data-open="true"] .t-acc-chevron {
  transform: scaleY(-1);
}

/* ============================================================
   22 Toast (la pila y la cosmetica viven en toast.css)
   ============================================================ */
.t-toast {
  opacity: 0;
  transform: translateY(var(--toast-distance)) scale(var(--toast-scale));
  filter: blur(var(--toast-blur));
  will-change: transform, opacity, filter;
  transition:
    opacity var(--toast-close) var(--toast-ease),
    transform var(--toast-close) var(--toast-ease),
    filter var(--toast-close) var(--toast-ease);
}
.t-toast.is-open {
  opacity: 1;
  transform: translateY(0) scale(1);
  filter: blur(0);
  transition:
    opacity var(--toast-open) var(--toast-ease),
    transform var(--toast-open) var(--toast-ease),
    filter var(--toast-open) var(--toast-ease);
}

/* ============================================================
   26 Spinning counter (motion.js construye las columnas)
   ============================================================ */
.t-reel { display: inline-flex; align-items: center; height: var(--reel-cell); font-variant-numeric: tabular-nums; }
.t-reel-col {
  position: relative; height: var(--reel-cell); overflow: hidden;
  -webkit-mask-image: linear-gradient(to bottom, transparent 0%, #000 22%, #000 78%, transparent 100%);
  mask-image: linear-gradient(to bottom, transparent 0%, #000 22%, #000 78%, transparent 100%);
}
.t-reel-strip { display: flex; flex-direction: column; will-change: transform, filter; }
.t-reel-strip.is-spinning { filter: blur(var(--reel-spin-blur)); }
.t-reel-digit { height: var(--reel-cell); display: flex; align-items: center; justify-content: center; }

/* ============================================================
   27 Toggle — sobre el pseudo-elemento de los switches del
   producto (.toggle-switch__track::before, .check-toggle::after)
   se aplica con estas mismas variables en su CSS
   ============================================================ */
.t-toggle { transition: background var(--toggle-track) var(--toggle-ease); }
.t-toggle-thumb { translate: 0 0; will-change: translate; }
.t-toggle[data-on="true"] .t-toggle-thumb { translate: var(--toggle-travel) 0; }
.t-toggle.is-init[data-on="true"] .t-toggle-thumb { animation: t-toggle-on var(--toggle-dur) var(--toggle-ease) both; }
.t-toggle.is-init[data-on="false"] .t-toggle-thumb { animation: t-toggle-off var(--toggle-dur) var(--toggle-ease) both; }
@keyframes t-toggle-on {
  0% { translate: 0 0; }
  55% { translate: calc(var(--toggle-travel) + var(--toggle-ov1)) 0; }
  80% { translate: calc(var(--toggle-travel) - var(--toggle-ov2)) 0; }
  100% { translate: var(--toggle-travel) 0; }
}
@keyframes t-toggle-off {
  0% { translate: var(--toggle-travel) 0; }
  55% { translate: calc(0px - var(--toggle-ov1)) 0; }
  80% { translate: var(--toggle-ov2) 0; }
  100% { translate: 0 0; }
}

/* ============================================================
   28 Thinking states (etapas de un proceso, no LLM)
   ============================================================ */
.t-think { position: relative; display: inline-block; text-align: center; }
.t-think-sizer { display: block; visibility: hidden; white-space: nowrap; }
.t-think-text {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  display: block;
  color: var(--think-base);
  white-space: nowrap;
  transform: translateY(0);
  filter: blur(0);
  opacity: 1;
  transition:
    transform var(--think-swap) var(--think-ease),
    filter var(--think-swap) var(--think-ease),
    opacity var(--think-swap) var(--think-ease);
  will-change: transform, filter, opacity;
}
.t-think-text::before {
  content: attr(data-text);
  position: absolute;
  inset: 0;
  pointer-events: none;
  background-image: linear-gradient(90deg,
    transparent 0%, transparent 40%,
    var(--think-highlight) 50%,
    transparent 60%, transparent 100%);
  background-size: 400% 100%;
  background-repeat: no-repeat;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  -webkit-text-fill-color: transparent;
  animation: t-think-shimmer var(--think-shimmer) linear infinite;
}
@keyframes t-think-shimmer {
  0%   { background-position: 100% 0; }
  100% { background-position: 0% 0; }
}
.t-think-text.is-exit {
  transform: translateY(calc(var(--think-distance) * -1));
  filter: blur(var(--think-blur));
  opacity: 0;
}
.t-think-text.is-enter-start {
  transition: none;
  transform: translateY(var(--think-distance));
  filter: blur(var(--think-blur));
  opacity: 0;
}

/* ============================================================
   15 Shimmer text — el texto se duplica en data-text y ::before
   pinta el brillo recortado a los glifos
   ============================================================ */
.t-shimmer {
  position: relative;
  display: inline-block;
  color: var(--shimmer-base);
}
.t-shimmer::before {
  content: attr(data-text);
  position: absolute;
  inset: 0;
  pointer-events: none;
  background-image: linear-gradient(
    90deg,
    transparent          0%,
    transparent         40%,
    var(--shimmer-highlight) 50%,
    transparent         60%,
    transparent        100%
  );
  background-size: var(--shimmer-band) 100%;
  background-repeat: no-repeat;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  -webkit-text-fill-color: transparent;
  animation: t-shimmer var(--shimmer-dur) var(--shimmer-ease) infinite;
}
@keyframes t-shimmer {
  0%   { background-position: 100% 0; }
  100% { background-position: 0% 0; }
}

/* ============================================================
   30 Streaming text — NumioMotion.stream() envuelve cada palabra
   en .t-stream-w y las resuelve en orden (opacidad + desenfoque)
   ============================================================ */
.t-stream-w {
  opacity: 0;
  filter: blur(var(--stream-blur));
  transition:
    opacity var(--stream-fade) var(--stream-ease),
    filter  var(--stream-fade) var(--stream-ease);
}
.t-stream-w.is-in {
  opacity: 1;
  filter: blur(0);
}

/* ============================================================
   31 Matrix dot loader
   ============================================================ */
.t-matrix {
  display: grid;
  grid-template-columns: repeat(4, 2px);
  grid-auto-rows: 2px;
  gap: 2px;
}
.t-matrix i {
  display: block;
  background: var(--matrix-base);
  animation: t-matrix-pulse var(--matrix-cycle) var(--matrix-ease) infinite;
  animation-delay: calc(var(--d, 0) * 1ms);
}
.t-matrix i.is-gap { visibility: hidden; animation: none; }
@keyframes t-matrix-pulse {
  0%, 45%, 100% { background-color: var(--matrix-base); }
  15%           { background-color: var(--matrix-active); }
}
.t-matrix--md { grid-template-columns: repeat(4, 3px); grid-auto-rows: 3px; gap: 3px; }

/* ============================================================
   32 Banner stacking, variante "lite": un banner suelto (sync,
   impago, exceso de plan, avisos) entra y sale como el primero
   de la pila. Sin apilado real: aqui los banners no se solapan.
   ============================================================ */
.nu-banner {
  filter: blur(0);
  will-change: transform, opacity, filter;
  transition:
    transform var(--stack-open) var(--stack-ease),
    opacity   var(--stack-open) var(--stack-ease),
    filter    var(--stack-open) var(--stack-ease);
}
.nu-banner.is-enter {
  transition: none;
  transform: translateY(calc(var(--stack-rise) * -1)) scale(var(--stack-scale));
  opacity: 0;
  filter: blur(var(--stack-blur));
}
.nu-banner.is-enter.nu-banner--from-bottom {
  transform: translateY(var(--stack-rise)) scale(var(--stack-scale));
}
.nu-banner.is-leaving {
  transform: translateY(calc(var(--stack-rise) * -1)) scale(var(--stack-scale));
  opacity: 0;
  filter: blur(var(--stack-blur));
  pointer-events: none;
  transition:
    transform var(--stack-close) var(--stack-ease),
    opacity   var(--stack-close) var(--stack-ease),
    filter    var(--stack-close) var(--stack-ease);
}
.nu-banner.is-leaving.nu-banner--from-bottom {
  transform: translateY(var(--stack-rise)) scale(var(--stack-scale));
}

/* ============================================================
   33 Tarjeta interactiva (M.tilt): tilt 3D, foco, esquinas, parallax
   Estructura: la tarjeta lleva .t-tilt y como primer hijo un
   <span class="t-tilt-fx"> (::before = foco radial que sigue al
   puntero por --mx/--my; ::after = las cuatro esquinas visor).
   Los pseudo del host quedan libres para cada pantalla. Los hijos
   con data-depth se desplazan en parallax desde --tilt-nx/--tilt-ny,
   que escribe el JS en la tarjeta (un solo nodo por frame).
   ============================================================ */
.t-tilt { transform-origin: 50% 50%; }
/* Mientras el rAF manda sobre transform, ninguna transicion de la
   pantalla puede "arrastrarlo" (dashboard.css tiene transform 150ms). */
.t-tilt.is-tilting {
  will-change: transform;
  transition: box-shadow var(--duration-quick, 150ms) var(--ease-out, ease-out);
}
.t-tilt-fx {
  position: absolute; inset: 0;
  border-radius: inherit; overflow: hidden;
  pointer-events: none; z-index: 0;
}
.t-tilt-fx::before {
  content: ""; position: absolute; inset: -1px; opacity: 0;
  background: radial-gradient(var(--tilt-glow-size) circle at var(--mx, 50%) var(--my, 50%), var(--tilt-glow-color), transparent 62%);
  transition: opacity var(--tilt-return) var(--ease-smooth-out, cubic-bezier(0.22, 1, 0.36, 1));
}
.t-tilt.is-hover .t-tilt-fx::before { opacity: 1; }
/* Esquinas: ocho gradientes (dos por esquina) que crecen de 0 a --tilt-vf-len.
   background-size se anima, asi que se "dibujan" sin @property. */
.t-tilt-fx::after {
  --c: var(--tilt-vf-color); --t: var(--tilt-vf-thick); --l: var(--tilt-vf-len);
  content: ""; position: absolute; inset: var(--tilt-vf-inset); opacity: 0;
  background-image:
    linear-gradient(var(--c), var(--c)), linear-gradient(var(--c), var(--c)),
    linear-gradient(var(--c), var(--c)), linear-gradient(var(--c), var(--c)),
    linear-gradient(var(--c), var(--c)), linear-gradient(var(--c), var(--c)),
    linear-gradient(var(--c), var(--c)), linear-gradient(var(--c), var(--c));
  background-repeat: no-repeat;
  background-position: top left, top left, top right, top right, bottom left, bottom left, bottom right, bottom right;
  background-size: 0 var(--t), var(--t) 0, 0 var(--t), var(--t) 0, 0 var(--t), var(--t) 0, 0 var(--t), var(--t) 0;
  transition:
    background-size var(--tilt-vf-dur) var(--ease-smooth-out, cubic-bezier(0.22, 1, 0.36, 1)),
    opacity var(--duration-quick, 150ms) var(--ease-out, ease-out);
}
.t-tilt.is-hover .t-tilt-fx::after,
.t-tilt:focus-visible .t-tilt-fx::after {
  opacity: 1;
  background-size: var(--l) var(--t), var(--t) var(--l), var(--l) var(--t), var(--t) var(--l), var(--l) var(--t), var(--t) var(--l), var(--l) var(--t), var(--t) var(--l);
}
.t-tilt[data-tilt-viewfinder="false"] .t-tilt-fx::after { display: none; }
/* Parallax: las capas con data-depth se mueven HACIA el cursor, en
   proporcion a su profundidad, sin JS por hijo. */
.t-tilt [data-depth] {
  transform: translate3d(
    calc(var(--tilt-nx, 0) * var(--depth, 0) * var(--tilt-parallax)),
    calc(var(--tilt-ny, 0) * var(--depth, 0) * var(--tilt-parallax)), 0);
}

/* ============================================================
   34 Confirmar en sitio (M.confirmInline)
   El boton queda envuelto en .t-confirm-host (inline-flex, relative)
   y el panel .t-confirm sale FLOTANDO a su lado (absolute), a la
   derecha si cabe y si no a la izquierda (lo decide el JS midiendo
   el contenedor que recorta): en una celda de tabla no empuja nada
   ni obliga a hacer scroll. La tapa de la papelera gira al abrir.
   ============================================================ */
.t-confirm-host { position: relative; display: inline-flex; align-items: center; vertical-align: middle; }
.t-confirm {
  position: absolute; top: 50%; z-index: 6;
  width: max-content; max-width: min(380px, 90vw);   /* el host mide lo que el boton: sin esto el panel encoge a min-content */
  display: flex; align-items: center; gap: 4px;
  padding: 3px 4px 3px 8px;
  background: var(--surface-base, #fff);
  border: 1px solid var(--line, #E8E3F8);
  border-radius: 14px;
  box-shadow: 0 8px 22px rgba(15, 11, 30, .12);
  opacity: 0; visibility: hidden; pointer-events: none;
  transform: translateY(-50%) scaleX(.7);
  transition:
    opacity var(--confirm-dur) var(--confirm-ease),
    transform var(--confirm-dur) var(--confirm-ease),
    visibility 0s linear var(--confirm-dur);
}
.t-confirm[data-side="right"] { left: calc(100% + 6px); transform-origin: left center; }
.t-confirm[data-side="left"]  { right: calc(100% + 6px); transform-origin: right center; flex-direction: row-reverse; padding: 3px 8px 3px 4px; }
.t-confirm__in { display: contents; }
.t-confirm-host.is-open .t-confirm {
  opacity: 1; visibility: visible; pointer-events: auto;
  transform: translateY(-50%) scaleX(1);
  transition-delay: 0s;
}
.t-confirm__hint {
  font-size: .74rem; line-height: 1.25; font-weight: 600; color: var(--confirm-yes-bg);
  max-width: 320px; white-space: normal; padding: 0 4px 0 0;
}
.t-confirm[data-side="left"] .t-confirm__hint { padding: 0 0 0 4px; }
.t-confirm__yes, .t-confirm__no {
  flex: 0 0 auto; width: 26px; height: 26px; border-radius: 8px; border: 0; padding: 0;
  display: inline-grid; place-items: center; cursor: pointer;
  transition: transform var(--duration-quick, 150ms) var(--ease-out, ease-out), filter var(--duration-quick, 150ms) var(--ease-out, ease-out);
}
.t-confirm__yes { background: var(--confirm-yes-bg); color: #fff; }
.t-confirm__no  { background: var(--confirm-no-bg); color: var(--confirm-no-fg); }
.t-confirm__yes:hover, .t-confirm__no:hover { filter: brightness(.94); }
.t-confirm__yes:active, .t-confirm__no:active { transform: scale(.94); }
.t-confirm__yes:focus-visible, .t-confirm__no:focus-visible { outline: 2px solid var(--brand, #6B5BFF); outline-offset: 2px; }
/* La papelera: la tapa se levanta mientras el panel esta abierto. */
.t-trash { display: inline-block; vertical-align: -0.125em; overflow: visible; }
.t-trash__lid { transform-origin: 4px 6px; transition: transform var(--confirm-dur) var(--ease-bounce, cubic-bezier(0.34, 1.36, 0.64, 1)); }
.t-confirm-host.is-open .t-trash__lid { transform: translate(-1.5px, -2px) rotate(-22deg); }

/* ============================================================
   35 Carpeta animada (M.folder / .nu-folder)
   Trasera con pestana, tres fichas y delantera con perspectiva. Al
   pasar el raton las fichas se abren en abanico; .is-open, o un
   ancestro .is-over (arrastrando ficheros), levanta la delantera.
   ============================================================ */
.nu-folder {
  position: relative; display: inline-block;
  width: var(--folder-size); height: calc(var(--folder-size) * .8);
  perspective: calc(var(--folder-size) * 8);
  line-height: 0; vertical-align: middle;
}
.nu-folder--sm { --folder-size: 48px; }
.nu-folder--lg { --folder-size: 96px; }
.nu-folder > i { position: absolute; display: block; }
.nu-folder__back {
  inset: 14% 0 0 0; border-radius: 9%; background: var(--folder-c);
}
.nu-folder__back::before {          /* la pestana */
  content: ""; position: absolute; left: 0; top: -12%; width: 42%; height: 16%;
  background: var(--folder-c); border-radius: 30% 30% 0 0 / 100% 100% 0 0;
}
.nu-folder__card {
  left: 14%; bottom: 20%; width: 72%; height: 66%;
  background: #fff; border-radius: 6%;
  box-shadow: 0 2px 6px rgba(15, 11, 30, .14);
  background-image: repeating-linear-gradient(180deg, transparent 0 18%, rgba(107,91,255, .14) 18% 22%);
  background-size: 60% 100%; background-position: 20% 30%; background-repeat: no-repeat;
  transform-origin: 50% 100%;
  transition: transform var(--folder-dur) var(--folder-ease);
}
.nu-folder__card--1 { background-color: #f4f1ff; }
.nu-folder__card--3 { background-color: #faf9ff; }
.nu-folder__front {
  left: 0; right: 0; bottom: 0; height: 60%; border-radius: 9%;
  background: linear-gradient(180deg, var(--folder-c2), var(--folder-c));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .28), 0 4px 10px rgba(107,91,255, .25);
  transform-origin: 50% 100%;
  transition: transform var(--folder-dur) var(--folder-ease);
}
/* Abanico al pasar el raton (o con foco en el control que la contiene). */
.nu-folder:hover .nu-folder__card--1, .nu-folder.is-hover .nu-folder__card--1, :is(label, button, a):hover .nu-folder .nu-folder__card--1, :is(label, button, a):focus-visible .nu-folder .nu-folder__card--1 { transform: translate(-24%, -30%) rotate(-14deg); }
.nu-folder:hover .nu-folder__card--2, .nu-folder.is-hover .nu-folder__card--2, :is(label, button, a):hover .nu-folder .nu-folder__card--2, :is(label, button, a):focus-visible .nu-folder .nu-folder__card--2 { transform: translate(0, -40%); }
.nu-folder:hover .nu-folder__card--3, .nu-folder.is-hover .nu-folder__card--3, :is(label, button, a):hover .nu-folder .nu-folder__card--3, :is(label, button, a):focus-visible .nu-folder .nu-folder__card--3 { transform: translate(24%, -30%) rotate(14deg); }
/* Abierta del todo: arrastrando ficheros encima (.is-over lo ponen las pantallas) o .is-open. */
.nu-folder.is-open .nu-folder__front, .is-over .nu-folder .nu-folder__front { transform: rotateX(-38deg); }
.nu-folder.is-open .nu-folder__card--1, .is-over .nu-folder .nu-folder__card--1 { transform: translate(-30%, -46%) rotate(-18deg); }
.nu-folder.is-open .nu-folder__card--2, .is-over .nu-folder .nu-folder__card--2 { transform: translate(0, -58%); }
.nu-folder.is-open .nu-folder__card--3, .is-over .nu-folder .nu-folder__card--3 { transform: translate(30%, -46%) rotate(18deg); }
/* Al soltar: las fichas entran de golpe (rebote corto). */
.nu-folder.is-drop .nu-folder__card { transition-timing-function: var(--ease-bounce, cubic-bezier(0.34, 1.36, 0.64, 1)); transition-duration: var(--duration-fast, 250ms); }

/* ============================================================
   37 Lista de tareas (M.tasklist / .nu-tasklist)
   ============================================================ */
.nu-tasklist { list-style: none; margin: 0; padding: 0; }
.nu-task {
  display: flex; align-items: center; gap: 12px;
  padding: 9px 8px; border-radius: 10px;
  transition: background var(--duration-quick, 150ms) var(--ease-out, ease-out);
}
.nu-task:hover { background: var(--surface-soft, #f6f5fb); }
.nu-task__check {
  flex: 0 0 22px; width: 22px; height: 22px; border-radius: 50%; padding: 0;
  border: 1.5px solid var(--line-strong, #cbd5e1); background: transparent; cursor: pointer;
  display: grid; place-items: center; color: #fff;
  transition: background var(--duration-fast, 250ms) var(--ease-out, ease-out), border-color var(--duration-fast, 250ms) var(--ease-out, ease-out), transform var(--duration-quick, 150ms) var(--ease-bounce, cubic-bezier(0.34, 1.36, 0.64, 1));
}
.nu-task__check:hover { border-color: var(--task-accent); transform: scale(1.08); }
.nu-task__check:focus-visible { outline: 2px solid var(--task-accent); outline-offset: 2px; }
.nu-task__check svg { width: 13px; height: 13px; fill: none; stroke: currentColor; stroke-width: 2.6; stroke-linecap: round; stroke-linejoin: round; }
.nu-task__check svg path { stroke-dasharray: 24; stroke-dashoffset: 24; }
.nu-task__body { min-width: 0; flex: 1 1 auto; display: flex; flex-direction: column; gap: 1px; }
.nu-task__label {
  align-self: flex-start; max-width: 100%;   /* el tachado mide el texto, no la fila */
  font-size: .9rem; font-weight: 600; color: var(--ink, #0F0B1E); line-height: 1.3;
  background: linear-gradient(currentColor, currentColor) no-repeat 0 55% / 0% 1.5px;
  transition: background-size var(--task-strike) var(--ease-smooth-out, cubic-bezier(0.22, 1, 0.36, 1)), color var(--task-strike) var(--ease-out, ease-out);
}
.nu-task__meta { font-size: .76rem; color: var(--muted, #6B7280); }
.nu-task__side { flex: 0 0 auto; font-size: .76rem; font-weight: 600; color: var(--muted, #6B7280); white-space: nowrap; }
.nu-task[data-done="1"] .nu-task__check { background: var(--task-accent); border-color: var(--task-accent); }
.nu-task[data-done="1"] .nu-task__check svg path { stroke-dashoffset: 0; transition: stroke-dashoffset var(--task-strike) var(--ease-smooth-out, cubic-bezier(0.22, 1, 0.36, 1)) 60ms; }
.nu-task[data-done="1"] .nu-task__label { background-size: 100% 1.5px; color: var(--muted, #6B7280); }
.nu-task[data-done="1"] .nu-task__meta, .nu-task[data-done="1"] .nu-task__side { opacity: .7; }
.nu-task--alerta .nu-task__side { color: var(--color-negative, #e11d48); }

/* ============================================================
   38 Orb de puntos (M.orb / .t-orb)
   El canvas pinta con --orb-c, que cambia por data-state.
   ============================================================ */
.t-orb { display: inline-block; width: var(--orb-size, 40px); height: var(--orb-size, 40px); line-height: 0; --orb-c: var(--orb-idle); }
.t-orb > canvas { display: block; width: 100%; height: 100%; }
.t-orb[data-state="listening"] { --orb-c: var(--orb-listening); }
.t-orb[data-state="thinking"]  { --orb-c: var(--orb-thinking); }
.t-orb[data-state="speaking"]  { --orb-c: var(--orb-speaking); }
.t-orb[data-state="error"]     { --orb-c: var(--orb-error); }

/* ============================================================
   39 Campana con contador (M.bell)
   ============================================================ */
.t-bell__icon { display: inline-block; transform-origin: 50% 15%; }
.t-bell__icon.is-ringing { animation: t-bell-swing var(--bell-dur) var(--ease-out, ease-out) both; }
.t-bell__icon.is-ringing.is-hard { animation-name: t-bell-swing-hard; }
@keyframes t-bell-swing {
  0% { transform: rotate(0); } 12% { transform: rotate(14deg); } 28% { transform: rotate(-11deg); }
  44% { transform: rotate(7deg); } 60% { transform: rotate(-4deg); } 76% { transform: rotate(2deg); } 100% { transform: rotate(0); }
}
@keyframes t-bell-swing-hard {
  0% { transform: rotate(0); } 12% { transform: rotate(24deg); } 28% { transform: rotate(-19deg); }
  44% { transform: rotate(12deg); } 60% { transform: rotate(-7deg); } 76% { transform: rotate(3deg); } 100% { transform: rotate(0); }
}

/* ============================================================
   Utilidades Numio
   ============================================================ */
/* El unico spinner del producto: sustituye a los ocho que habia. */
.nu-spinner {
  display: inline-block;
  width: 1em;
  height: 1em;
  border: 2px solid currentColor;
  border-right-color: transparent;
  border-radius: 50%;
  vertical-align: -0.15em;
  animation: nu-spin 0.8s var(--ease-linear, linear) infinite;
}
@keyframes nu-spin { to { transform: rotate(360deg); } }

/* Barras de porcentaje que crecen desde la izquierda (nunca se anima
   width: solo transform). */
.t-bar-grow {
  transform-origin: left center;
  animation: t-bar-grow var(--duration-slow, 400ms) var(--ease-smooth-out, cubic-bezier(0.22, 1, 0.36, 1)) backwards;
}
@keyframes t-bar-grow { from { transform: scaleX(0); } }

/* Fundido corto generico para paneles que aparecen (tabs, estados). */
.nu-fade-in {
  animation: nu-fade-in var(--duration-fast, 250ms) var(--ease-smooth-out, cubic-bezier(0.22, 1, 0.36, 1)) backwards;
}
@keyframes nu-fade-in { from { opacity: 0; } }

/* ============================================================
   prefers-reduced-motion — bloque global (hay ~60 CSS con
   .15s heredados y solo 8 con guard) mas las excepciones: un
   indicador de progreso congelado se lee como "colgado".
   transition-delay no se toca: los `visibility 0s linear Xms`
   siguen funcionando.
   ============================================================ */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
  .nu-spinner,
  .fa-spin,
  .t-matrix i,
  .nu-xbtn.is-loading::after {
    animation-duration: 0.8s !important;
    animation-iteration-count: infinite !important;
  }
  .t-matrix i { animation-duration: var(--matrix-cycle) !important; }
  /* Guards de las fichas (se conservan tal cual piden). */
  .t-resize, .t-dropdown, .t-modal, .t-modal-backdrop, .t-panel-slide,
  .t-icon-swap .t-icon, .t-text-swap, .nu-text-swap > span, .t-badge, .t-badge-dot,
  .t-skel-skeleton, .t-skel-content, .t-tabs-pill, .t-tt, .t-stagger-line,
  .t-acc-panel, .t-acc-panel-inner, .t-acc-chevron, .t-toast, .t-reel-strip,
  .t-think-text, .nu-banner { transition: none !important; }
  .t-modal { filter: none !important; transform: none !important; }
  .t-digit-group .t-digit, .t-badge, .t-stagger-item, .t-bar-grow, .nu-fade-in,
  .t-toggle-thumb, .t-skel-skeleton.is-pulsing > *, .nu-pulse { animation: none !important; }
  .is-shaking { animation: none !important; transform: none !important; }
  .t-success-check { animation: none !important; opacity: 1; }
  .t-success-check svg path { animation: none !important; stroke-dashoffset: 0 !important; }
  .t-reel-strip { filter: none !important; }
  .t-think-text { transform: none !important; filter: none !important; }
  .t-shimmer::before { animation: none !important; }
  .t-stream-w { transition: none !important; filter: none !important; opacity: 1 !important; }
  .t-think-text::before { display: none !important; }
  .t-tilt [data-depth] { transform: none !important; }
  .t-tilt-fx::before, .t-tilt-fx::after { transition: none !important; }
  .t-confirm, .t-trash__lid { transition: none !important; }
  .nu-folder > i { transition: none !important; }
  .t-tabs-goo > * { transition: none !important; animation: none !important; }
  .nu-task, .nu-task__check, .nu-task__check svg path, .nu-task__label { transition: none !important; }
  .t-bell__icon { animation: none !important; }
}
