/* ===========================================================================
 * bbmotion.css — LA CAPA DE MOVIMIENTO. Un vocabulario, no efectos sueltos.
 * ===========================================================================
 * Miguel, 24 sep 2026: *"can you add animations and motions so bridgebi can
 * feel smooth to use"*.
 *
 * El app NO estaba quieto: ya hay 95 `@keyframes` repartidos en 20 archivos, y
 * todos respetan `prefers-reduced-motion`. Lo que faltaba es lo de ARRIBA —
 * los momentos del app, no los de una página: cambiar de página, montar una
 * sección, abrir un panel, apretar una fila, dibujar una serie, ver que un
 * número cambió. Eso no se sentía suave porque simplemente no existía.
 *
 * LAS CINCO REGLAS (y por qué cada una)
 *
 * 1. UN MOVIMIENTO EXPLICA UN CAMBIO DE ESTADO, O NO SE MONTA. Nada decora.
 *    Si al quitarlo la pantalla se entiende igual de bien, sobraba.
 *
 * 2. NUNCA SE ANIMA EL VALOR DE UN NÚMERO. El "count-up" que sube de 0 a 5,520
 *    pinta, durante medio segundo, cifras que nadie midió — en un app cuya
 *    doctrina entera es que un número en pantalla nombra su ventana y su
 *    población. Un número que cambió se señala con un DESTELLO detrás
 *    (`.bbm-flash`), que dice "esto es nuevo" sin afirmar nada falso.
 *
 * 3. UN REFRESCO NO SE ANIMA. `BBRepaint` aterriza manteniendo el scroll: si
 *    además la página entrara con un fade, cada actualización parecería una
 *    recarga. La entrada es para la PRIMERA pintura; lo que llega después
 *    cambia y ya. (`docs/rules/loading.md`.)
 *
 * 4. SÓLO SE ANIMAN `opacity`, `transform`, `clip-path` Y COLOR. Animar
 *    `height`, `width`, `top` o `margin` recalcula layout en cada cuadro y en
 *    una tabla de 400 filas eso se siente peor que no animar nada.
 *
 *    Y el corolario que ya cobró aquí: **un `transform` en un ancestro crea un
 *    bloque contenedor y atrapa a cualquier `position:fixed` adentro** — el FAB
 *    de soporte es fixed. Por eso la entrada de página anima a los HIJOS, nunca
 *    al contenedor de la página, y `bbmotion.js` quita la clase al terminar.
 *
 * 5. NADA SE REPITE. Un bucle es para una espera de verdad (el cargador), no
 *    para una tarjeta. Una animación infinita en una pantalla que se mira ocho
 *    horas es ruido.
 *
 * DURACIONES. Medidas de las referencias de CollectUI leídas esta semana
 * (Sandbox Limits, LLM Leaderboard, Mono Charts): los cortes reales caen en
 * 167 · 250 · 333 · 417ms, con `ease-out` o `material-standard` casi siempre y
 * un `spring` sólo donde algo aterriza. El app ya tenía la escala
 * (`--bb-t-fast/base/slow` = .12/.16/.28s) y estas tres cubren todo lo de
 * abajo; se añaden dos curvas y una duración para lo que entra de lejos.
 * ------------------------------------------------------------------------- */

:root {
  /* La salida es más rápida que la entrada: lo que se va no hay que leerlo. */
  --bb-t-enter:  .26s;
  --bb-t-exit:   .16s;
  /* `ease-out` medido (0,0,.58,1) para lo que entra y aterriza. */
  --bb-ease-out: cubic-bezier(0, 0, .58, 1);
  /* El aterrizaje con un pelo de sobrepaso. No es un rebote: 4%, lo justo para
     que un panel se sienta sólido al parar. */
  --bb-ease-land: cubic-bezier(.22, 1.04, .36, 1);
}

/* La puerta global. Un solo sitio la apaga: sin esto habría que acordarse en
   cada regla, y la que se olvide es la que marea a alguien. */
@media (prefers-reduced-motion: reduce) {
  .bbm-page > *, .bbm-in, .bbm-panel, .bbm-flash, .bbm-ind::after {
    animation: none !important;
    transition: none !important;
  }
}

/* --- 1 · ENTRADA DE PÁGINA -----------------------------------------------
 * Los bloques de la página entran escalonados, 22ms uno detrás de otro. El
 * escalón no es adorno: da el ORDEN DE LECTURA de la pantalla en el momento en
 * que aparece, que es justo cuando el ojo no sabe dónde empezar.
 * Se anima a los hijos, nunca al contenedor (regla 4), y `bbmotion.js` quita
 * la clase al terminar para no dejar un `transform` vivo.
 * Tope de 8: a partir de ahí el escalón se acumula y la última tarjeta
 * aparecería medio segundo tarde. */
.bbm-page > * { animation: bbm-rise var(--bb-t-enter) var(--bb-ease-out) both; }
/* El escape: un hijo que no puede animarse (ver bbmotion.js). Va DESPUÉS de la
   regla de arriba para ganarle por orden, sin `!important`. */
.bbm-page > .bbm-skip { animation: none; }
.bbm-page > :nth-child(1) { animation-delay: 0ms; }
.bbm-page > :nth-child(2) { animation-delay: 22ms; }
.bbm-page > :nth-child(3) { animation-delay: 44ms; }
.bbm-page > :nth-child(4) { animation-delay: 66ms; }
.bbm-page > :nth-child(5) { animation-delay: 88ms; }
.bbm-page > :nth-child(6) { animation-delay: 110ms; }
.bbm-page > :nth-child(7) { animation-delay: 132ms; }
.bbm-page > :nth-child(n+8) { animation-delay: 154ms; }

@keyframes bbm-rise {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: none; }
}

/* Un bloque suelto que aparece después (una sección que se despliega). */
.bbm-in { animation: bbm-rise var(--bb-t-enter) var(--bb-ease-out) both; }

/* --- 2 · FILA DE TABLA ---------------------------------------------------
 * Hover y press. NO se usa `transform` en una fila: en una `<table>` mueve la
 * fila fuera de su celda y en una lista con `overflow` la recorta. El relieve
 * lo da la sombra, que no toca el layout.
 * El press (`:active`) es lo que hace que un clic se sienta contestado antes
 * de que llegue el dato — que es la mitad de la sensación de "rápido". */
.bbm-row {
  transition: background-color var(--bb-t-fast) ease-out,
              box-shadow var(--bb-t-fast) ease-out;
}
.bbm-row:hover { background: var(--bb-hover); box-shadow: var(--bb-sh-hairline); }
.bbm-row:active { background: var(--bb-hover-2); }

/* Un control que se puede apretar. 1.5% de escala: se siente, no se ve. */
.bbm-press { transition: transform var(--bb-t-fast) var(--bb-ease-out); }
.bbm-press:active { transform: scale(.985); }

/* --- 3 · PANEL / HOJA / MODAL -------------------------------------------
 * Entra desde su propio borde, para que la dirección diga de dónde salió: la
 * hoja lateral desde la derecha, la inferior desde abajo, el modal desde su
 * centro. Un panel que aparece sin venir de ningún sitio obliga a buscar qué
 * cambió en la pantalla. */
.bbm-panel { animation: bbm-pop var(--bb-t-enter) var(--bb-ease-land) both; }
.bbm-panel[data-from="right"]  { animation-name: bbm-right; }
.bbm-panel[data-from="bottom"] { animation-name: bbm-bottom; }
.bbm-panel[data-from="left"]   { animation-name: bbm-left; }
.bbm-panel.is-out { animation: bbm-fade-out var(--bb-t-exit) ease-in both; }

@keyframes bbm-pop    { from { opacity: 0; transform: scale(.985) translateY(4px); } to { opacity: 1; transform: none; } }
@keyframes bbm-right  { from { opacity: 0; transform: translateX(16px); } to { opacity: 1; transform: none; } }
@keyframes bbm-left   { from { opacity: 0; transform: translateX(-16px); } to { opacity: 1; transform: none; } }
@keyframes bbm-bottom { from { opacity: 0; transform: translateY(18px); } to { opacity: 1; transform: none; } }
@keyframes bbm-fade-out { to { opacity: 0; transform: translateY(3px); } }

/* El velo de un modal entra aparte y más lento que el panel: si los dos entran
   juntos, el panel parece llegar tarde. */
.bbm-veil { animation: bbm-veil var(--bb-t-enter) ease-out both; }
@keyframes bbm-veil { from { opacity: 0; } to { opacity: 1; } }

/* --- 4 · LA SERIE SE DIBUJA ----------------------------------------------
 * Una línea de tendencia se traza de izquierda a derecha en 560ms la PRIMERA
 * vez que se pinta. Es el único movimiento largo del app y se gana el permiso:
 * el trazo recorre el eje del tiempo, así que el movimiento dice lo mismo que
 * el dato. No se repite en un refresco (regla 3) — `bbmotion.js` marca el
 * elemento.
 * `--len` lo escribe el JS con `getTotalLength()`; sin él la regla no hace
 * nada, que es el fallo correcto. */
.bbm-draw { stroke-dasharray: var(--len); stroke-dashoffset: var(--len);
  animation: bbm-draw .56s var(--bb-ease-out) forwards; }
@keyframes bbm-draw { to { stroke-dashoffset: 0; } }
/* Los puntos y el área aparecen DESPUÉS de que el trazo llegó. */
.bbm-draw-after { animation: bbm-veil var(--bb-t-base) ease-out .42s both; }

/* --- 5 · UN NÚMERO CAMBIÓ ------------------------------------------------
 * El destello, no el conteo (regla 2). Un tinte detrás de la cifra que se
 * apaga en 900ms: dice "esto es nuevo desde que lo miraste" sin pintar ni un
 * número que no sea el bueno.
 * Va en `box-shadow` y no en `background` para no tener que saber qué fondo
 * tiene la celda debajo. */
.bbm-flash { animation: bbm-flash .9s ease-out both; border-radius: var(--bb-r-chip); }
@keyframes bbm-flash {
  0%   { box-shadow: 0 0 0 4px var(--bb-gold-tint); }
  100% { box-shadow: 0 0 0 4px transparent; }
}
.bbm-flash.is-up   { --bbm-tint: var(--bb-green-tint); }
.bbm-flash.is-down { --bbm-tint: var(--bb-red-tint); }
.bbm-flash.is-up, .bbm-flash.is-down { animation-name: bbm-flash-tone; }
@keyframes bbm-flash-tone {
  0%   { box-shadow: 0 0 0 4px var(--bbm-tint); }
  100% { box-shadow: 0 0 0 4px transparent; }
}

/* --- 6 · EL INDICADOR DE PESTAÑA ----------------------------------------
 * La barra bajo la pestaña activa se DESLIZA en vez de saltar. Es el
 * movimiento que más barato compra la sensación de continuidad: la pantalla
 * cambió, pero se ve de dónde a dónde.
 * Necesita `position:relative` en el contenedor y `--x`/`--w` escritos por el
 * JS de cada barra; sin ellos no se pinta. */
.bbm-ind { position: relative; }
.bbm-ind::after {
  content: ''; position: absolute; left: 0; bottom: 0; height: 2px;
  width: var(--w, 0); transform: translateX(var(--x, 0));
  background: var(--bb-accent); border-radius: 2px;
  transition: transform var(--bb-t-slow) var(--bb-ease-land),
              width var(--bb-t-slow) var(--bb-ease-land);
}

/* ===========================================================================
 * SEGUNDA TANDA (24 sep 2026) — "these are too basic and simple".
 * ===========================================================================
 * Tenía razón: la primera tanda era un fade con un translate. Esto es lo que
 * se ve en CollectUI cuando alguien sabe lo que hace, medido con el MCP:
 *
 *   · `1f45fee9` view transitions (@armondme) — el elemento VIAJA de una vista
 *     a la otra. Traslados de 109–435px en 103–276ms, `ease-out` al entrar y
 *     `material-accelerate` al salir. De ahí el FLIP compartido y el reordenar.
 *   · `2ea8a8f9` "Hiding row borders on hover" (@Jamie_Merrill) — al pasar el
 *     cursor, la fila PIERDE sus líneas y se despega de la rejilla como una
 *     banda propia. 260ms medidos. De ahí `.bbm-lift`.
 *   · `1b230ab4` Mono Charts — el trazo con cabezal.
 *
 * La diferencia entre las dos tandas, dicha de una vez: la primera anunciaba
 * que algo apareció; esta CONSERVA LA IDENTIDAD de lo que se movió. Cuando una
 * fila se convierte en una ficha y la ficha vuelve a ser fila, no hace falta
 * volver a buscar dónde estabas — y eso no es cosmética, es la diferencia
 * entre leer una tabla y perder el hilo diez veces por hora.
 * ------------------------------------------------------------------------- */

/* --- 7 · LA FILA SE DESPEGA DE LA REJILLA --------------------------------
 * Ref `2ea8a8f9`, 260ms. Al pasar el cursor, las líneas de arriba y abajo se
 * apagan y la fila queda como una banda con su propio relieve. El truco es que
 * las líneas de la tabla se pintan en la FILA, no entre filas, así que basta
 * con apagar las suyas y las de la vecina de arriba.
 * Sigue sin usar `transform` (regla 4): el relieve es sombra y color. */
.bbm-grid { --bbm-line: var(--bb-line); }
.bbm-grid > * { box-shadow: inset 0 -1px 0 var(--bbm-line);
  transition: box-shadow .26s var(--bb-ease-out), background-color .26s var(--bb-ease-out); }
.bbm-grid > *:last-child { box-shadow: none; }
.bbm-lift:hover, .bbm-grid > *:hover {
  background: var(--bb-surface);
  box-shadow: 0 0 0 1px var(--bb-line), 0 2px 10px rgba(13, 42, 94, .07);
  border-radius: 8px;
}
/* La de arriba también apaga su línea, o queda un filo pegado al borde
   superior de la banda. `:has()` es lo que hace esto posible sin JS. */
.bbm-grid > *:has(+ *:hover) { box-shadow: none; }

/* --- 8 · ELEMENTO COMPARTIDO (FLIP) -------------------------------------
 * Lo escribe `BBMotion.flip()` con la Web Animations API, así que aquí sólo
 * vive lo que el motor necesita: un elemento que viaja no puede quedar debajo
 * de nada, y mientras viaja no debe recibir clics. */
.bbm-fly { position: fixed; z-index: 9000; pointer-events: none; margin: 0;
  will-change: transform, opacity; }

/* --- 9 · EL NÚMERO CAMBIA, DESLIZANDO -----------------------------------
 * El viejo sube y se va, el nuevo entra desde abajo. En pantalla hay, en todo
 * momento, SÓLO valores medidos: el de antes y el de ahora. Eso es lo que lo
 * separa de un count-up, que inventa 400 cifras por el camino (regla 2).
 * El alto se fija a `1em` y se recorta, o el layout salta en cada cambio. */
.bbm-roll { position: relative; display: inline-block; height: 1em; overflow: hidden;
  vertical-align: bottom; }
.bbm-roll > span { display: block; }
.bbm-roll > .old { position: absolute; inset: 0; animation: bbm-roll-out .34s var(--bb-ease-out) both; }
.bbm-roll > .new { animation: bbm-roll-in .34s var(--bb-ease-land) both; }
@keyframes bbm-roll-out { to { transform: translateY(-100%); opacity: 0; } }
@keyframes bbm-roll-in  { from { transform: translateY(100%); opacity: 0; } to { transform: none; opacity: 1; } }

/* --- 10 · EL TRAZO LLEVA CABEZAL ----------------------------------------
 * La línea no sólo se dibuja: un punto la encabeza y la etiqueta del último
 * valor viaja con él hasta el final. Así el ojo llega al número en vez de
 * tener que buscarlo cuando el trazo para.
 * `offset-path` mueve el cabezal por la MISMA curva, así que no hay una
 * segunda animación que pueda desincronizarse. */
.bbm-head { offset-path: path(var(--d)); offset-distance: 0%;
  animation: bbm-head .56s var(--bb-ease-out) forwards; }
@keyframes bbm-head { to { offset-distance: 100%; } }
.bbm-head-lbl { animation: bbm-veil var(--bb-t-base) ease-out .5s both; }

/* --- 11 · CONTENIDO DE PESTAÑA, EN LA DIRECCIÓN DEL VIAJE ---------------
 * Si la pestaña nueva está a la derecha, el contenido entra desde la derecha.
 * Una pestaña que siempre entra igual pierde la única información gratis que
 * tiene el gesto: hacia dónde te moviste. */
.bbm-tab-in  { animation: bbm-tab-r .24s var(--bb-ease-out) both; }
.bbm-tab-in[data-dir="left"] { animation-name: bbm-tab-l; }
@keyframes bbm-tab-r { from { opacity: 0; transform: translateX(14px); } to { opacity: 1; transform: none; } }
@keyframes bbm-tab-l { from { opacity: 0; transform: translateX(-14px); } to { opacity: 1; transform: none; } }

@media (prefers-reduced-motion: reduce) {
  .bbm-lift, .bbm-grid > *, .bbm-fly, .bbm-roll > *, .bbm-head, .bbm-head-lbl, .bbm-tab-in {
    animation: none !important; transition: none !important; offset-path: none !important;
  }
}
