/* ============================================================================
   Sistema OSECAC — Movimiento
   Catorce gestos con nombre. La regla que los ordena:
   si un movimiento no informa un cambio de estado, no va.
   Todo respeta prefers-reduced-motion: los estados cambian igual, sin recorrido.
   ========================================================================== */

/* 1 · Entrada escalonada — el orden de lectura de la pantalla.
       Cada elemento lleva --d con su desfase. Ésta es la versión de los mockups (transición
       atada a `[data-vivo]`); la del producto es la animación `entrar-al-listado` de
       `portal.css`, con los tiempos y el recorrido de los tokens `--cascada-*` (T164) en las filas de
       `.grilla`, `.listado > *`, `.lista > *` y `[data-en-cascada] > *`. */
.entra{opacity:0;transform:translateY(7px)}
[data-vivo] .entra{
  opacity:1;transform:none;
  transition:opacity var(--dur-media) var(--curva-salida),transform var(--dur-larga) var(--curva-salida);
  transition-delay:var(--d,0ms);
}
/* Variante sin desplazamiento. Va en TODO lo que tenga un transform propio en
   otro estado: el panel de detalle (que en móvil es una hoja) y los botones
   (que se elevan al hover). Con .entra, el transform:none de la entrada gana
   por especificidad y mata el otro estado — la hoja queda siempre abierta, o
   el botón no sube nunca. Ya pasó dos veces. */
.entra-fundido{opacity:0}
[data-vivo] .entra-fundido{opacity:1;transition:opacity var(--dur-media) var(--curva-salida);transition-delay:var(--d,0ms)}

/* 🔀 **Al filtrar, la lista que llega ya no entra con `data-fundido`** (`T170` → `T181`). Ese fundido partía
   siempre de `--opacidad-atenuada`, aunque no hubiera habido espera, y el brillo caía un cuadro. Lo que
   vuelve de un filtro lo anima el reemplazo (gesto 14, al final de esta hoja), desde la opacidad viva. */

/* 2 · Rodado de importes — la cifra cambió, y hacia dónde.
       El recorrido lo hace JS; el sistema sólo garantiza el ancho estable.

   ⚠️ **La regla de arriba tiene una excepción, y está fechada** (07/09/2026). Las
   cifras que encabezan una pantalla ruedan **al entrar a esa pantalla**, aunque
   entrar no sea un cambio de estado. La pidió el negocio: son lo primero que se mira
   y el recorrido es lo que hace que se las mire.

   La excepción es **a entrar y sólo a entrar** — no al filtrar, ni al buscar, ni al
   recargar, ni al volver con el atrás—, y se marca cifra por cifra con `data-rodar`.
   Eso último importa: `.valor` también dibuja el CUIT en el legajo de la empresa, y
   un identificador que cuenta hacia arriba miente sobre lo que es.

   No habilita animar cualquier cosa. La regla sigue siendo la de arriba; ésta es la
   única excepción y tiene nombre, fecha y alcance. */
.rueda{font-variant-numeric:tabular-nums;font-feature-settings:"tnum"}

/* 3 · Barrido de fila — dónde está el cursor en una grilla ancha.
       JS fija --rx con la posición del puntero. Ver .grilla en componentes.css */

/* 4 · Traspaso al detalle — el panel ya habla de otra empresa. */
.traspasa::after{
  content:"";position:absolute;inset:0;pointer-events:none;
  background:linear-gradient(90deg,var(--acento),transparent 42%);
  opacity:.14;animation:barrido var(--dur-larga) var(--curva-salida) forwards;
}
@keyframes barrido{from{opacity:.18;transform:translateX(-12px)}to{opacity:0;transform:none}}

/* 5 · Hoja sobre velo — el resto de la app queda en suspenso.
       Ver .velo y .hoja en componentes.css

       🔀 **T169 · abre como cierra.** La entrada dura `--dur-larga` con `--curva-hoja` y la salida
       `--dur-corta` con `--curva-salida`; el velo anima sólo `opacity`. `portal.js` fuerza un cuadro
       cerrado antes de encender `data-hoja-abierta` (sin él no hay entrada). Con `reduce`, un
       fundido de `--dur-corta` y ningún desplazamiento (más abajo).

       Y la misma familia, **lo que flota junto a su control** (decisión del dueño, 08/10/2026): la
       lista del desplegable y el panel del calendario entran con un fundido y un asentamiento de
       escala que **nace en el control** —`portal.js` escribe el `transform-origin` en
       `colocarFlotante`: el borde que toca al control, en el centro del control—. No es un
       recorrido: es de dónde viene. Sólo `opacity` y `transform`.

       🔀 **Y salen** (`T181`). Entraban con un `@keyframes` y se iban en cero cuadros: `hidden` los
       sacaba del árbol de render. Ahora son **transiciones**: entran desde `@starting-style` en
       `--dur-corta` y salen en `--dur-instante` entre los mismos extremos, con `display` discreto
       (`allow-discrete`) para que la lista siga en pantalla mientras se va. Reabrir a mitad de la
       salida parte del valor vivo —una transición se revierte desde donde iba—, y como la que se va
       es la misma lista y no una copia, **nunca hay dos**. `portal.js` devuelve la lista del
       desplegable a su `.desplegable` recién cuando terminó de irse (mover un nodo corta sus
       transiciones). Mientras se va no recibe el puntero. */
.desplegable__lista,.calendario__panel{
  transition:opacity var(--dur-instante) var(--curva-salida),
             transform var(--dur-instante) var(--curva-salida),
             display var(--dur-instante) var(--curva-salida) allow-discrete;
}
.desplegable__lista[hidden],.calendario__panel[hidden]{opacity:0;transform:scale(.96);pointer-events:none}
.desplegable__lista:not([hidden]),.calendario__panel:not([hidden]){
  opacity:1;transform:none;
  transition:opacity var(--dur-corta) var(--curva-salida),
             transform var(--dur-corta) var(--curva-salida),
             display var(--dur-corta) var(--curva-salida) allow-discrete;
}
@starting-style{
  .desplegable__lista:not([hidden]),.calendario__panel:not([hidden]){opacity:0;transform:scale(.96)}
}

/* 6 · El sello — el acta quedó cancelada, con fuerza de acto. */
@keyframes sellar{
  0%  {opacity:0;  transform:translate(-50%,-50%) rotate(-13deg) scale(1.75)}
  22% {opacity:.96;transform:translate(-50%,-50%) rotate(-13deg) scale(1)}
  28% {transform:translate(-50%,-50%) rotate(-12.2deg) scale(1.03)}
  34% {transform:translate(-50%,-50%) rotate(-13deg) scale(1)}
  78% {opacity:.96}
  100%{opacity:0;  transform:translate(-50%,-50%) rotate(-13deg) scale(1)}
}

/* 7 · Latido de intimación — ese expediente corre plazo legal.
       Único movimiento permanente del sistema. Ver .estado--intimada */
@keyframes latido{
  0%,100%{opacity:1;transform:scale(1)}
  50%    {opacity:.35;transform:scale(.72)}
}

/* 8 · Deriva de aurora — si lo que mirás tiene deuda o está saldado. */
.aurora-lienzo{position:absolute;inset:0;z-index:0;pointer-events:none;background:var(--aurora);transition:filter var(--dur-cielo) var(--curva-cielo)}
.aurora-lienzo[data-saldo="deuda"]{filter:hue-rotate(-16deg) saturate(1.14)}
.aurora-lienzo[data-saldo="saldado"]{filter:hue-rotate(12deg) saturate(.9)}

/* 9 · Continuidad entre páginas — el mismo contenido, no una pantalla nueva.
       Sin esto, cambiar de pantalla es un parpadeo blanco y lo que se fue no se
       fue a ningún lado: simplemente dejó de estar. Con la transición el
       navegador tiene las dos instantáneas y hace la continuidad solo.

       Sirve para las **dos** formas de cambiar de estado, con la misma
       maquinaria y el mismo nombre en el elemento:

       - una **navegación completa** — la declara este `@view-transition`;
       - un **intercambio de fragmento** — lo pide quien lo hace, con
         `hx-swap="… transition:true"` (ver la nómina de CUILes).

       Es **mejora progresiva pura**: donde no exista, el cambio es instantáneo,
       que es lo que hacía antes. Y `view-transition-name` va en el elemento que
       tiene que reconocerse de un estado al otro —**único por página**, o el
       navegador descarta la transición entera—. */
@view-transition{navigation:auto}

/* 10 · El giro del cielo — cambiaste de tema, y el fondo entero es el que cambia.
        ────────────────────────────────────────────────────────────────────────
        El fondo del sistema deja de ser un `background` del `<body>` y pasa a
        ser **dos capas apiladas**, las dos fijas al viewport y las dos detrás
        de todo (`z-index` negativo):

          .cielo__disco    el PISO. Un círculo de 340vh centrado abajo del
                           viewport, pintado con `--cielo-degrade` —día arriba,
                           noche abajo, crepúsculo en la costura—. Cambiar de
                           tema lo gira **180°**: la costura barre la pantalla
                           de lado a lado y amanece, o anochece.
          .cielo__manchas  las MANCHAS de la aurora, que no giran. Están las dos
                           siempre en el DOM y se cruzan por opacidad, porque un
                           `background` no transiciona entre dos pilas de
                           degradés: la que se va se desvanece mientras la que
                           llega aparece.

        ⚠️ **El disco gira, las manchas no**, y es a propósito. Nuestra aurora
        son manchas *ubicadas* —una arriba a la izquierda, otra arriba a la
        derecha, otra abajo—; si girasen con el disco, la identidad del portal
        daría una vuelta de campana. Lo que gira es el cielo detrás de ellas.

        Lo dispara el atributo `data-tema` del `<html>`, que cambia `portal.js`
        cuando el servidor confirma el tema nuevo. **No hay ningún otro estado**:
        ni clase mientras dura, ni temporizador — el gesto entero es CSS, y el JS
        sólo cambia un atributo. Sin JavaScript no hay giro y el cambio de tema
        llega por navegación completa, ya pintado, que es exactamente lo que
        pasaba antes de este gesto. */
.cielo{position:fixed;inset:0;overflow:hidden;z-index:-2;pointer-events:none;
  background:var(--cielo-piso)}
.cielo__disco{
  position:absolute;left:50%;top:100%;border-radius:50%;
  /* ⚠️ `340vh` a secas deja franjas a los costados en una pantalla ancha y
     baja —1920×500 da 1700 px de disco para 1920 de ancho—. El `max` con
     `vw` lo cubre en los dos sentidos. */
  width:max(340vh,220vw);height:max(340vh,220vw);
  transform-origin:50% 50%;
  transform:translate(-50%,-46%) rotate(var(--cielo-giro));
  background:var(--cielo-degrade);
  /* ⚠️ **Nada de filtros acá.** Un filtro obliga a rasterizar este elemento aparte,
     y con 2816 px de lado eso no entra en el presupuesto de un cuadro. Lo único
     que se anima es la transformación, que va por el compositor y es barata. */
  transition:transform var(--dur-cielo) var(--curva-tema);
}
/* 🔴 **El desenfoque del viaje se sacó, y no por gusto.** El diseño de referencia
   lo tiene, y la primera versión lo copió con `filter:blur(9px)` sobre el disco.
   El usuario reportó dos veces que la animación parpadeaba al final; medido con
   los cuadros que entrega el compositor, en la ventana de 1000 ms del gesto:

       filter:blur(9px) sobre el disco ...... 27 cuadros
       backdrop-filter blur(9px), viewport .. 35 cuadros, salto de 61
       backdrop-filter blur(4px), viewport .. 28 cuadros, salto de 53
       backdrop-filter blur(2px), viewport .. 34 cuadros, salto de 63
       **sin desenfoque** .................... **52 cuadros, rampa monótona**

   ⚠️ **El radio no es lo que cuesta: es que haya un desenfoque.** Bajarlo de 9 px
   a 2 px no mejora nada. Con desenfoque, la rampa de diferencia entre cuadros
   pega un salto y vuelve —un cuadro perdido y otro que se pone al día—, y eso es
   exactamente lo que se ve como parpadeo, siempre sobre el final.

   Sin él el barrido queda suave y el gesto se lee mejor. Es la divergencia más
   grande respecto del diseño de referencia, y la única que se tomó midiendo. */

.cielo__manchas{position:absolute;inset:0;opacity:0;
  transition:opacity var(--dur-cielo) var(--curva-tema)}

/* 🔀 **Todo sigue al cielo** (`T181`; antes, desde `T170`, sólo el piso y el texto). Lo que cambia de
   color con el tema son los **tokens** de la capa 2, registrados con `@property` en `tokens.css`, y
   transicionan **una vez**, acá, en `:root`, en `--dur-cielo` con `--curva-tema` —la misma curva que el
   disco y las manchas—. El piso (`html`, `.cielo`), el texto del `<body>`, cada panel, borde, botón,
   enlace y campo **usan** esos tokens: reciben el mismo valor interpolado en el mismo cuadro, sin una
   transición propia que pueda atrasarse. Medido en la galería: con `background-color` y `color`
   transicionados elemento por elemento, el texto heredado llegaba cuadros detrás de su fondo (cada hijo
   reinicia la transición de `color` que hereda) y a +60 ms había paneles diurnos sobre un cielo
   nocturno. Fuera del cambio de tema esta transición no corre: los tokens no cambian. */
:root{
  transition-property:
    --cielo-piso, --superficie, --superficie-alta, --cromo, --superficie-hover, --superficie-activa,
    --borde, --borde-vivo, --borde-fuerte, --borde-zona, --separador, --texto,
    --texto-2, --texto-3, --acento, --acento-tinta, --barrido, --velo,
    --sello, --barra, --barra-hover, --estado-deuda, --estado-deuda-borde, --estado-deuda-fondo,
    --estado-sin, --estado-sin-borde, --estado-sin-fondo, --estado-cancelada, --estado-cancelada-borde, --estado-cancelada-fondo,
    --estado-plan, --estado-plan-borde, --estado-plan-fondo, --estado-intimada, --estado-intimada-borde, --estado-intimada-fondo,
    --estado-activo, --estado-activo-borde, --estado-activo-fondo, --estado-inactivo, --estado-inactivo-borde, --estado-inactivo-fondo,
    --aviso, --aviso-fondo, --aviso-borde, --aviso-marca;
  transition-duration:var(--dur-cielo);
  transition-timing-function:var(--curva-tema);
}
/* **Mientras el tema cambia, ningún componente transiciona su color.** Un `.boton` con `background
   var(--dur-corta)` perseguiría al token cuadro a cuadro y llegaría tarde. `portal.js` pone
   `data-tema-cambiando` en `<html>` durante `--dur-cielo` (gesto 10) y estas piezas —las que declaran
   una transición de color en `componentes.css`, `portal.css`, `base.css` y este archivo; la lista la
   vigila `test_t181_movimiento_estatico.py`— dejan sólo la sombra, que no es un token registrado y
   cambia de golpe si nadie la acompaña. Fuera del cambio cada una conserva su hover. El conmutador
   (`.tema .perilla`, `.tema .astro`) no está: su viaje es su gesto. */
html[data-tema-cambiando] :is(.boton,.campo,.desplegable__control,.calendario__control,.casilla__marca,
    .nav a,.pestana,.mas__item,.ir-a-configuracion,.tema,.quitar,.conmutador > a,.conmutador > button,
    .persona > summary,.casilla-rol,.mini,.barra--elegible,.casilla__aro,.grilla tbody tr),
html[data-tema-cambiando] .tema::before,html[data-tema-cambiando] .mini::before,
html[data-tema-cambiando] ::-webkit-scrollbar-thumb{
  transition:box-shadow var(--dur-cielo) var(--curva-tema) !important;
}
.cielo__manchas--noche{background:var(--aurora-manchas-nocturna)}
.cielo__manchas--dia{background:var(--aurora-manchas-diurno)}
[data-tema="nocturna"] .cielo__manchas--noche{opacity:1}
[data-tema="diurno"] .cielo__manchas--dia{opacity:1}

/* Las nubes viven en la mitad de día del disco, así que se ven sólo cuando esa
   mitad quedó arriba. No hay que apagarlas: el giro se las lleva. */
/* 🟡 **Quietas.** La primera versión las traía a la deriva y las estrellas
   titilando, como el diseño de referencia. Se sacó por dos motivos, y el primero
   es la regla que encabeza este archivo: **si un movimiento no informa un cambio
   de estado, no va**, y el único movimiento permanente del sistema es el latido
   de intimación, que sí informa —ese expediente corre plazo legal—. El segundo lo
   reportó el usuario mirando la pantalla: detrás de un panel con
   `backdrop-filter`, un fondo que nunca se queda quieto se lee como que los
   títulos y el texto **parpadean**.

   🔀 **Y las estrellas terminaron yéndose enteras** (07/09/2026). Quedaron dos
   capas de puntos en la mitad de noche, con una nota que decía que iban «más
   tenues que en el diseño de referencia» —opacidad .22— para que no compitieran
   con una grilla de importes. Que una textura necesite esconderse para no
   molestar es la señal de que no iba: en el diseño de referencia las estrellas
   **son** el tema de la pantalla, y acá el tema es un acta. La mitad de noche va
   limpia, que además es lo que hace legible el degradé del horizonte.

   Las nubes se quedan: son las que dan el cielo del lado del día. */
.cielo__nube{position:absolute;border-radius:50%;background:rgba(255,255,255,.5)}
.cielo__nube:nth-child(1){left:14%;top:33%;width:40vh;height:9vh;filter:blur(3.2vh)}
.cielo__nube:nth-child(2){left:58%;top:26%;width:30vh;height:7vh;filter:blur(3vh);
  background:rgba(255,255,255,.42)}
.cielo__nube:nth-child(3){left:34%;top:43%;width:56vh;height:8vh;filter:blur(4vh);
  background:rgba(255,228,214,.5)}


/* 11 · La espera — el servidor está pensando, y se ve DÓNDE.
   ────────────────────────────────────────────────────────────────────────────
   Hasta el 15/09/2026 el sistema no tenía ningún estado de carga. Cero. Cada
   acción es un viaje al servidor (HTMX), y entre el clic y la respuesta no pasaba
   nada en pantalla. En el escritorio de la oficina eso se disimula; en la tablet
   del inspector, con señal mala, son segundos de interfaz muerta en los que la
   única lectura posible es «no anduvo» — y el reflejo es volver a tocar.

   🔴 **Los primeros 140 ms no muestran nada, y es la mitad del diseño.** Una
   respuesta que llega en 80 ms con un indicador de por medio se ve PEOR que sin
   nada: es un parpadeo. El indicador existe para las esperas que se notan, no
   para todas. La demora la aplica `portal.js`, que es quien pone el atributo.

   **Se marca la región que cambia, no la pantalla.** Un velo global diría «la app
   está ocupada»; lo que hace falta decir es «esta tabla se está actualizando» —y
   sobre todo, cuando el objetivo es `closest tr`, «ESTA fila». Por eso el estado
   va sobre el objetivo del intercambio. El `<body>` queda excluido a propósito
   (`portal.js`): apagar la pantalla entera es exactamente lo que no queremos.

   **El hilo no es la única señal, y hasta el 19/09/2026 sí lo era.** Acá decía
   «no hay ruleta», citando taste-frontend §4.5 —*«avoid generic circular
   spinners»*—, y esa regla contestaba **una** pregunta: *¿qué región se está
   actualizando?* Para eso el hilo sigue siendo mejor que cualquier ruleta: ocupa
   cero espacio, no desplaza nada al aparecer y dice **dónde**.

   Lo que la regla no cubría es la otra pregunta, *¿hay algo en curso?*, que
   aparece donde **no hay región que marcar**: el botón que ya se apretó, el
   arranque de la aplicación, el ingreso, un pie de listado. Ahí no había nada.

   🔵 **Desde el 19/09/2026 (T084) conviven, y se reparten por la pregunta que
   contestan:**

   | Señal | Dice | Dónde |
   |---|---|---|
   | el hilo | «**esta región** se actualiza» | sobre el objetivo del intercambio |
   | el isotipo girando (`.espera`) | «**hay algo en curso**» | donde no hay región: botón, arranque, ingreso |

   Y la ruleta genérica que taste-frontend rechaza **sigue rechazada**: la que gira
   acá es la marca, no un anillo de biblioteca. Es la misma diferencia que el
   desplegable y el calendario propios: el sistema dibuja lo suyo.

   ⚠️ **Lo que no se afloja**, porque está medido: los 140 ms de demora valen para
   las dos señales, el velo global sigue prohibido y **el indicador no desplaza
   nada al aparecer** — por eso el isotipo del botón va superpuesto y no al lado. */

[data-esperando]{cursor:progress}

/* 🔴 El ancla del hilo no puede pisar un `position` que el elemento ya tiene (**T201**). El hilo es un
   `::after` `absolute`: necesita un ancestro posicionado, y un objetivo `static` pasa a `relative`
   para serlo. Pero `[data-esperando]{position:relative}` tenía la especificidad de cualquier regla de
   clase (0,1,0) y se declara después: le ganaba a `.resumen-fijo{position:sticky}`, y a 1440 la isla
   de `/emitir-recibo/`, desplazada, pasaba de `top:16` a `top:−397` (fuera de la vista) mientras duraba la
   espera de un recálculo. `:where()` baja la regla a especificidad cero: se aplica a lo que ninguna
   otra regla posicionó (`static`, el valor por defecto) y cede ante cualquiera que sí lo hizo, en el
   orden en que se cargue. */
:where([data-esperando]){position:relative}

/* El atenuado va en los HIJOS, no en el elemento: si fuera en el elemento, el
   hilo —que es un ::after suyo— se atenuaría con ellos y perdería contraste. */
[data-esperando] > *{
  opacity:var(--opacidad-atenuada);pointer-events:none;
  transition:opacity var(--dur-corta) var(--curva-salida);
}

/* El hilo se mete 6 px hacia adentro y redondea las puntas. No es cosmética: el
   objetivo puede tener esquinas redondeadas —un panel lleva 14 o 16 px— y una
   barra a ancho completo sobre `top:0` se sale de la curva por ambos lados. Con
   el margen entra en la forma de cualquier objetivo, tenga radio o no lo tenga
   (una fila de tabla no lo tiene, y ahí los 6 px se leen como intención). */
.hilo-de-espera,[data-esperando]::after{
  content:"";position:absolute;inset-inline:6px;top:0;height:2px;z-index:2;
  border-radius:2px;
  background:linear-gradient(90deg,transparent,var(--acento),transparent);
  background-size:38% 100%;background-repeat:no-repeat;
  animation:hilo var(--dur-hilo) linear infinite;
}
@keyframes hilo{from{background-position:-38% 0}to{background-position:138% 0}}

/* El disparador: quién registró el clic. HTMX pone `.htmx-request` solo, apenas
   sale el pedido — así que la demora de 140 ms la pone acá un `animation-delay`
   con `forwards`, sin un timer en JavaScript. */
.boton.htmx-request,.mini.htmx-request,.pestana.htmx-request{
  animation:espera-del-disparador 0s linear var(--demora-espera) forwards;
}
@keyframes espera-del-disparador{to{opacity:.62;cursor:progress}}

/* El isotipo girando (T084). La curva es `--curva-cielo` —la misma
   `cubic-bezier(.62,.01,.32,1)` del entregable—: no es un giro de motor, respira
   una vez por vuelta. */
@keyframes espera-gira{to{rotate:360deg}}
.espera{animation:espera-gira var(--dur-espera) var(--curva-cielo) infinite}

/* 🔴 **El botón que envía no puede cambiar de ancho.** Si el isotipo entrara en
   la fila, la barra de acciones entera saltaría justo en el cuadro en que el
   operador acaba de apretar — el peor momento posible para mover algo.

   La etiqueta pasa a `color:transparent` en vez de ocultarse: el texto sigue
   midiendo lo mismo, así que el botón conserva su caja al píxel. El isotipo se
   apoya encima, centrado sobre las dos diagonales.

   Y **apaga el atenuado del disparador**: dos señales sobre el mismo botón es
   ruido, no énfasis. El .62 se queda para `.mini` y `.pestana`, que no reciben
   isotipo porque no tienen dónde ponerlo. */
.boton[data-espera]{color:transparent;animation:none;opacity:1;cursor:progress}
.boton[data-espera] > .espera{
  position:absolute;left:50%;top:50%;translate:-50% -50%;
  /* Se pinta con `color` y no con `fill`: el símbolo trae su propio
     `fill="currentColor"` dentro del shadow tree del `<use>`. */
  color:var(--acento-tinta);
}
/* 🔑 **El botón lleno es el único caso que no puede llevar el color de la marca.**
   Ya es del color de acento, así que el acento encima sería invisible. El valor
   por defecto —el acento— lo pone `componentes.css`, y acá se pisan los tres
   casos de botón: el lleno con su tinta, el secundario de vuelta al acento
   (porque la regla de arriba también lo alcanza) y el de peligro con el suyo,
   que es su propio acento. El rótulo que acompaña al isotipo sí se queda en el
   color del texto: es una etiqueta, no la marca. */
.boton--secundario[data-espera] > .espera{color:var(--acento)}
.boton--peligro[data-espera] > .espera{color:var(--estado-intimada)}

/* La barra indeterminada del barrido: no dice cuánto falta —nadie lo sabe— sino
   que algo sigue pasando. */
@keyframes espera-avanza{
  0%{left:-38%;width:38%}50%{width:58%}100%{left:100%;width:32%}
}
.barrido__avance{animation:espera-avanza var(--dur-espera) var(--curva-salida) infinite}

/* El brillo que recorre el esqueleto. Es lo que lo distingue de una lista
   apagada de verdad: se ve que está esperando, no que está vacía. El desfase
   entre filas es lo que lo vuelve un barrido y no tres parpadeos sincronizados. */
@keyframes espera-brilla{from{transform:translateX(-100%)}to{transform:translateX(260%)}}
.esqueleto__fila::after{
  content:"";position:absolute;inset:0;width:40%;
  background:linear-gradient(90deg,transparent,var(--superficie-alta),transparent);
  animation:espera-brilla var(--dur-brillo) var(--curva-salida) infinite;
}
/* 🔴 **El desfase se cuenta con hermanos adyacentes, no con `nth-child`.**
   Medido en el banco: con `nth-child(2)` y `(3)` los retardos caían en la 1.ª y
   la 2.ª fila y **la tercera arrancaba en 0**, porque el rótulo del esqueleto es
   el primer hijo y corre la cuenta. O sea que la fila de abajo lideraba el
   barrido: tres parpadeos desordenados en vez de una luz que recorre.

   CSS no sabe decir «el enésimo de esta clase», pero `+` cuenta sólo filas,
   haya o no un rótulo arriba. */
.esqueleto__fila + .esqueleto__fila::after{animation-delay:var(--paso-del-brillo)}
.esqueleto__fila + .esqueleto__fila + .esqueleto__fila::after{animation-delay:calc(var(--paso-del-brillo) * 2)}


/* 12 · El hilo de la casilla — el borde se desenrolla y se vuelve el tilde.
   ────────────────────────────────────────────────────────────────────────────
   🔀 **Reemplaza al sello de la casilla** (20/09/2026). Antes era un rebote de escala con un aro
   que se abría en el acento: un gesto **celebratorio**, y el propio entregable lo dice al revés
   —*«al desmarcar todo va al revés y más rápido: deshacer nunca debe celebrarse»*—. En una lista
   de doscientas actas donde se eligen quince, celebrar cada tilde es ruido quince veces.

   El hilo dice lo mismo sin festejar: **el aro se desenrolla en su lugar y ese mismo hilo se
   convierte en el tilde**. Al marcar no queda casillero, sólo el trazo.

   🔴 **Los dos sentidos siguen siendo distintos, y sigue sin ser decoración.** Tildar es más
   lento y el tilde entra **después** de que el aro terminó de irse —se lee como una sola línea
   que se recoloca—; destildar va a dos tercios de la duración y sin espera, porque deshacer no
   se anuncia. Las proporciones son las del entregable (`1,15 · 0,8 · 0,5 · 0,66`) sobre
   `--dur-media`, y la curva es `--curva-cielo`, que **es** la del entregable:
   `cubic-bezier(.62,.01,.32,1)`.

   🔒 **Tres estados, no dos.** El tercero es `indeterminate` —«algunas de esta página»—, que lo
   dibuja la raya y lo decide el control nativo. Era el motivo por el que esta casilla no se
   dibujaba; ahora se dibuja **con** él.

   ⚠️ **Y sigue valiendo que sólo el gesto de la persona se anima.** La casilla del encabezado
   marca quince filas de una, y quince hilos desenrollándose a la vez es ruido, no información.
   Antes eso se resolvía al revés —`portal.js` ponía un atributo para **encender** la
   animación—, porque un `@keyframes` colgado de `:checked` arranca también al cargar la página.
   Un recorrido de transición no tiene ese problema —una transición no corre en el primer
   pintado—, así que ahora el JS **apaga**: marca el formulario mientras cambia las quince y lo
   suelta dos cuadros después. Menos estado y ningún atributo que pueda quedar pegado. */
/* ── Los tiempos, y por qué van en variables ──────────────────────────────── */
/* 🔑 **Los tiempos viajan en variables, y no es un gusto: es lo único que hace que el apagado
   pueda ganar.** Escrito de la forma obvia —cada estado con su `transition` y un
   `[data-sin-recorrido] .casilla__aro{transition:none}` al final— el apagado pesa (0,2,0) y la
   regla de estado `.casilla:has(input:checked) .casilla__aro` pesa (0,3,1), así que **pierde**:
   medido, las quince casillas del encabezado se animaban igual.

   Con los tiempos en variables la pelea se muda a `.casilla`, donde el apagado puede ser
   **estrictamente más pesado** —(0,3,1) contra (0,2,1)— en vez de empatar y depender del orden
   de las reglas. La `transition` se declara una sola vez por trazo y los estados sólo cambian
   **valores**. */
.casilla{
  --hilo-aro:calc(var(--dur-media) * .66);
  --hilo-tilde:calc(var(--dur-media) * .33);
  --hilo-espera:0s;
}
.casilla:has(input:checked){
  --hilo-aro:calc(var(--dur-media) * 1.15);
  --hilo-tilde:calc(var(--dur-media) * .8);
  /* La espera es lo que hace que se lea como **el mismo hilo**: el tilde no empieza hasta que el
     aro recorrió la mitad. Sin ella son dos trazos distintos pasando a la vez. */
  --hilo-espera:calc(var(--dur-media) * .5);
}
/* El apagado para el cambio que no hizo una persona. Lo pone `portal.js` alrededor de un cambio
   en lote y lo saca dos cuadros después: el recálculo del estado nuevo ocurre sin recorrido, así
   que las quince saltan a su marca y ninguna se anima. */
[data-sin-recorrido] .casilla:has(input){--hilo-aro:0s;--hilo-tilde:0s;--hilo-espera:0s}

.casilla__aro{
  /* 🔴 **`--texto-2` y no `--borde`, y está medido.** `--borde` es el borde de un *panel* —una
     insinuación de 12 % de blanco de noche, y de día blanco sobre casi blanco—; el aro de una
     casilla sin marcar es **el límite de un control**, y eso pide 3:1 contra lo que tiene al
     lado (WCAG 1.4.11). Con `--borde` medía **1,83:1 de noche y 1,08:1 de día**: en el tema
     claro la casilla vacía era un cuadrado blanco sin contorno. Se vio mirando la pantalla. */
  stroke:var(--texto-2);
  fill:var(--superficie);
  stroke-dasharray:72.9;
  stroke-dashoffset:0;
  transition:
    stroke-dashoffset var(--hilo-aro) var(--curva-cielo),
    stroke var(--hilo-aro) var(--curva-cielo),
    fill calc(var(--hilo-aro) / 2) var(--curva-cielo);
}
.casilla:has(input:checked) .casilla__aro{
  stroke:var(--acento);
  fill:transparent;
  stroke-dashoffset:72.9;
}

.casilla__tilde{
  stroke:var(--acento);
  stroke-dasharray:20;
  stroke-dashoffset:20;
  transition:stroke-dashoffset var(--hilo-tilde) var(--curva-cielo) var(--hilo-espera);
}
.casilla:has(input:checked) .casilla__tilde{stroke-dashoffset:0}

/* La raya del estado intermedio no se desenrolla: **aparece**. El aro se queda dibujado, porque
   «algunas» todavía es un casillero con algo adentro y no un tilde. */
.casilla__raya{
  stroke:var(--acento);
  stroke-dasharray:12;
  stroke-dashoffset:12;
  transition:stroke-dashoffset calc(var(--hilo-tilde) * 1.2) var(--curva-cielo);
}
.casilla:has(input:indeterminate) .casilla__raya{stroke-dashoffset:0}
/* Con `indeterminate` puesto el control puede informar `checked` a la vez: el tilde se apaga
   explícitamente para que nunca convivan los dos trazos. */
.casilla:has(input:indeterminate) .casilla__tilde{stroke-dashoffset:20;transition:none}

/* ── Presión al tocar (T117, T117c) ─────────────────────────────────────────
   `-webkit-tap-highlight-color:transparent` va global, una vez, en `base.css`
   —reemplaza al velo celeste que el navegador pinta por su cuenta—. En su lugar,
   **todo lo pulsable se hunde**. No es un gesto numerado —no afirma un cambio de
   estado, afirma que el sistema registró el toque— así que no entra a
   `movimiento.yaml`.

   🔀 **Hasta el 27/09/2026 el botón, la pestaña y la barra lateral no se hundían:**
   su `:active` volvía a `translateY(0)`, que es el reposo, y la spec decía que «el
   reposo ES la presión». Sin un puntero fino —la tableta, el teléfono— no hay
   `:hover` que deshacer, así que presionar no cambiaba nada. El dueño de producto
   vio la galería y pidió que el botón «se hunda un poco» al presionarlo.

   | pieza | `:active` | dónde |
   |---|---|---|
   | `.boton` (todas), `.pestana`, `.mini`, `.conmutador > a` | `scale(--escala-presion-chip)` | `componentes.css`, `portal.css` |
   | `.nav a` | `scale(--escala-presion)` | `componentes.css` |
   | `.desplegable__control`, `.calendario__control` | `scale(--escala-presion)` + tinte (T166) | `componentes.css` |
   | `.casilla` | `scale(--escala-presion-chip)` **sobre la marca**, no sobre el área de 44 px, + disco de tinte (T166) | `componentes.css` |
   | `.campo` | sólo el tinte, sin escala: un campo que se achica mientras se escribe distrae (T166) | `componentes.css` |
   | `.accion` | `scale(.96)`, sin cambios: ya se hundía | `componentes.css` |
   | `.barra--elegible`, tarjeta de teléfono | `scale(--escala-presion)` + tinte | acá |
   | fila enlazada en escritorio | sólo el tinte | acá |

   Cada `:active` se declara **junto a su `:hover` y después**, con el mismo peso, y
   así gana cuando están los dos puestos: el botón que subió 2 px baja y se hunde en
   un solo recorrido. Sólo `transform` y `box-shadow`: nada que mueva el layout. Un
   solo par duración/curva para toda la familia, `--dur-corta` + `--curva-salida`,
   que también es el de la vuelta al soltar. */

/* Fila enlazada en escritorio/tableta — el tinte, no la escala: un
   `transform:scale()` en un `<tr>` de una tabla ancha se lee raro al lado de
   filas quietas que no se tocaron, y el tinte es el mismo lenguaje que ya usa
   `aria-selected`. */
.grilla tbody tr:has(.enlace-fila:active),
.barra--elegible:active{
  background-color:var(--superficie-activa);
  transition:background-color var(--dur-corta) var(--curva-salida);
}
/* La barra elegible sí se hunde: es una fila suelta en una columna angosta, no una fila de tabla
   entre otras quietas. Su transición de vuelta está en `portal.css`. */
.barra--elegible:active{
  transform:scale(var(--escala-presion));
  transition:background-color var(--dur-corta) var(--curva-salida),
             transform var(--dur-corta) var(--curva-salida);
}

/* Tarjeta de teléfono — acá sí, la escala: es el dedo, no el mouse, y no
   compite con ninguna fila vecina porque las tarjetas ya se separan con
   `border-bottom`. La transición va también en el reposo: si no, al soltar la
   tarjeta volvía a su tamaño de golpe, sin recorrido. */
@container app (max-width:40rem){
  .grilla tbody tr:has(.enlace-fila){
    transition:background-size var(--dur-corta) var(--curva-salida),
               transform var(--dur-corta) var(--curva-salida),
               background-color var(--dur-corta) var(--curva-salida);
  }
  .grilla tbody tr:has(.enlace-fila:active){
    transform:scale(var(--escala-presion));
    transition:transform var(--dur-corta) var(--curva-salida),
               background-color var(--dur-corta) var(--curva-salida);
  }
}

/* ── Transición entre secciones — gesto 9, en profundidad (T117, T117b) ─────
   🔀 **El menú principal y las pestañas del legajo ya no pasan por acá** (T117b). En T117 las dos
   se apoyaban en View Transitions: el menú en la de documento (`@view-transition`), el legajo en
   la de un intercambio (`transition:true`). Medido cuadro por cuadro en Chromium, a 1440 y a 390
   —la tabla está en `componentes.md § El relevo de sección`—, las dos parpadeaban y las
   dos saltaban:

   - **el menú**: durante ~500 ms había **dos** secciones marcadas a la vez —la foto vieja de la
     barra y la nueva, las dos opacas, y la barra es translúcida—; la vieja se apagaba de golpe al
     final, sin recorrido. Y la tabla nueva entraba vacía: sus filas caían escalonadas (gesto 1)
     durante 780 ms después de que la página ya estaba;
   - **el legajo**: el nombre, el CUIT y la ficha —que no cambian— se fundían y bajaban 6 px con
     la sección, porque `.cabecera__pagina` y `.cuerpo` tienen nombre propio y un intercambio del
     documento los arrastraba; la corredera desaparecía un cuadro y volvía a mitad de camino; y
     había un cuadro con la sección casi vacía (16 % de su tinta).

   Ahora los dos son **un relevo**: la barra no se vuelve a pintar nunca, su marca se desliza con
   una transición común —en todos los navegadores, no sólo en Chromium— y la sección nueva entra
   al lado de la vieja mientras la vieja se funde. Ver `portal.js`, «El relevo de sección».

   Lo que sigue acá es para las **otras** navegaciones de documento completo —abrir un acta,
   volver al legajo—, que siguen siendo navegaciones de verdad (ADR-0008) y siguen usando la
   continuidad del documento. */
.nav{view-transition-name:sof-nav}
.pestanas{view-transition-name:sof-pestanas}
.cabecera__chrome{view-transition-name:sof-cabecera}
.conmutador--vista,.conmutador--legajo{view-transition-name:sof-conmutador}
.conmutador .corredera{view-transition-name:sof-corredera}
/* 🔴 **Dos nombres, no uno — y está medido.** La spec original le daba el MISMO
   nombre (`sof-contenido`) a `.cabecera__pagina` y a `.cuerpo` con una lista de
   selectores separada por comas: eso no agrupa dos elementos bajo un nombre,
   los nombra a los DOS con el nombre repetido — y los dos existen a la vez en
   toda pantalla. Chromium lo reporta en la consola («Unexpected duplicate
   view-transition-name: sof-contenido») y **descarta la transición entera**. */
.contenido>.cabecera>.cabecera__pagina{view-transition-name:sof-cabecera-pagina}
.contenido>.cuerpo{view-transition-name:sof-contenido}

/* La barra, la cabecera y el conmutador NO se disuelven.

   🔴 **Y la foto vieja no se muestra** (T117b). T117 les sacaba la animación a las dos fotos
   —`animation:none` en la vieja y en la nueva— y las dos quedaban **opacas y apiladas** durante
   toda la transición. La barra es translúcida (`--superficie` sobre el cielo), así que por debajo
   de la nueva se leía la vieja: dos enlaces marcados a la vez, medido en los cuadros de +228 a
   +690 ms. La vieja se esconde y la nueva está desde el primer cuadro. */
::view-transition-old(sof-nav),::view-transition-old(sof-pestanas),
::view-transition-old(sof-cabecera),::view-transition-old(sof-conmutador){
  animation:none;opacity:0;
}
::view-transition-new(sof-nav),::view-transition-new(sof-pestanas),
::view-transition-new(sof-cabecera),::view-transition-new(sof-conmutador){
  animation:none;mix-blend-mode:normal;
}

/* La corredera SÍ se deja animar — es el shared element que viaja. Se le pone
   el par de la casa en vez del que trae el navegador por defecto (250ms,
   ease). */
::view-transition-group(sof-corredera){
  animation-duration:var(--dur-media);
  animation-timing-function:var(--curva-salida);
}

/* El contenido: fundido con desplazamiento corto, no el cross-fade genérico
   del navegador. */
::view-transition-old(sof-contenido),::view-transition-old(sof-cabecera-pagina){
  animation:sof-contenido-sale var(--dur-corta) var(--curva-salida) both;
}
::view-transition-new(sof-contenido),::view-transition-new(sof-cabecera-pagina){
  animation:sof-contenido-entra var(--dur-media) var(--curva-salida) both;
}
@keyframes sof-contenido-sale{to{opacity:0;transform:translateY(-6px)}}
@keyframes sof-contenido-entra{from{opacity:0;transform:translateY(6px)}}

/* ── El relevo de sección (T117b, T117c) ──────────────────────────────────────
   Lo dispara `portal.js` («El relevo de sección») sobre lo que cambia al cambiar de sección:
   `#pantalla` desde el menú principal, `#legajo-seccion` desde las pestañas del legajo. Cinco
   estados, y ninguno deja un cuadro vacío:

   | estado | quién lo pone | qué se ve |
   |---|---|---|
   | `data-esperando` | el gesto 11, a los 140 ms | la vieja atenuada y la espera encima |
   | `data-atenuada` | el relevo, en `beforeSwap` si estaba esperando | lo mismo, sin el pedido en vuelo |
   | `.relevo-sale` | el relevo, al llegar | la vieja, fuera del flujo, fundiéndose en `--dur-instante` |
   | `data-entrando` | el relevo, al llegar | la nueva, por encima de la vieja mientras las dos están |
   | `data-cascada` + `data-pieza` | el relevo, al llegar | las piezas de la nueva, entrando en orden de lectura |

   La vieja se va más rápido que lo que la nueva tarda en llegar: a los 120 ms ya no está, y la
   cabecera de la nueva —con `--curva-salida`, que arranca rápido, y sin espera— ya pasó el 70 % de
   su opacidad. Nunca se superponen dos pantallas legibles más de unos pocos cuadros, y nunca hay
   un cuadro sin ninguna.

   ⚠️ **En `#pantalla` no se mueve la fila de arriba de la cabecera** (`.cabecera__chrome`: la
   marca, quién está conectado, el engranaje). Es la misma en todas las secciones, así que entra
   quieta y en la vieja se esconde: fundir dos copias idénticas corridas 8 px se vería como un
   temblor. */
.hoja-de-la-pantalla{display:contents}
#legajo-vista{position:relative}
.legajo-seccion{position:relative}

/* `linear`, y probado lo contrario. Con `--curva-salida` la vieja caía antes de que la nueva
   subiera y el primer cuadro después del intercambio quedaba en 0,59 de opacidad sumada —una baja
   de brillo de un cuadro, que es exactamente lo que se lee como parpadeo—; con `linear` la suma no
   baja de 0,89 y lo que queda son dos o tres cuadros de las dos pantallas a la vez, fundiéndose. */
.relevo-sale{pointer-events:none;animation:sof-relevo-sale var(--dur-instante) linear forwards}
.relevo-sale > .cabecera > .cabecera__chrome{visibility:hidden}
/* La píldora de la espera se va en el cuadro del intercambio, y recién ahí arranca la cascada
   (T117c). Medido con 600 ms de latencia, fundiéndose con la vieja seguía «Cargando» encima de la
   cabecera nueva durante 130 ms. El contenido viejo, atenuado, sí se queda fundiéndose: es lo que
   evita el cuadro vacío mientras entra la primera pieza. En el legajo no: ahí el esqueleto **es**
   el contenido que se ve, y se funde con la vieja. */
.relevo-sale > .espera-global--seccion{visibility:hidden}
@keyframes sof-relevo-sale{to{opacity:0}}

/* La nueva por encima de la vieja mientras las dos están: la vieja es `position:absolute` y, sin
   esto, un bloque sin posicionar quedaría pintado debajo de ella. */
.legajo-seccion[data-entrando]{z-index:1}

/* 🔀 **La sección entra en cascada, no en bloque** (T117c, 27/09/2026). En T117b entraba entera
   —opacidad y 8 px sobre toda la sección—, para no repetir el parpadeo de T117: una tabla vacía
   que se llenaba sola durante 780 ms. El dueño de producto vio la galería y pidió lo otro: «que
   vayan apareciendo en cascada la información».

   La cascada es **la misma de la carga inicial** (gesto 1, `portal.css`): el mismo
   `entrar-al-listado` —subida, escala y opacidad, de tokens desde `T164`— en el mismo orden de lectura: la cabecera, los paneles,
   las filas. Lo que cambia es quién pone los tiempos. Al cargar el documento son `nth-child`; en
   un relevo los pone `portal.js` («La cascada del relevo»), pieza por pieza con `--d`, a
   `--cascada-paso` cada una y **con tope** en `--cascada-tope`: pasado el tope las piezas entran
   juntas y la cascada entera dura el tope más `--cascada-dur`, tenga la tabla diez filas o
   cincuenta.

   Y **no hay cuadro vacío**: la vieja se funde en `--dur-instante` mientras la cabecera de la
   nueva —que no espera— ya pasó la mitad de su opacidad.

   `:is(…)[data-cascada] [data-pieza]` pesa (0,3,0) y le gana a los `nth-child` de `portal.css`
   —que pesan hasta (0,2,2) y se cargan después—: una pieza del relevo toma su tiempo de acá, no
   del de la carga inicial. `backwards` y no `both`, por lo que explica `portal.css`: al terminar
   el `transform` vuelve a ser `none` de verdad. */
:is(.contenido,.legajo-seccion)[data-cascada] [data-pieza]{
  animation:entrar-al-listado var(--cascada-dur) var(--curva-cascada) backwards;
  animation-delay:var(--d,0ms);
}

/* La espera del menú: la sección vieja se atenúa —no la fila de arriba de la cabecera, que no es
   de la sección— y la píldora aparece encima. Sin hilo: la píldora ya dice lo mismo, y dos señales
   sobre la misma espera es ruido.

   🔴 **`[data-atenuada]` es la misma espera, pero ya sin pedido en vuelo** (T117b, medido con 600 ms
   de latencia). El gesto 11 le saca `data-esperando` al objetivo en `beforeSwap`, y hasta que el
   relevo lo marcaba como `.relevo-sale` la vieja pasaba **tres cuadros a opacidad 1** —de .4 a 1 y
   después a 0—: un destello de la pantalla vieja justo antes de irse (c028, +741 ms). El relevo le
   pone `data-atenuada` en `beforeSwap`, **antes** que el gesto 11 (en captura), y estas reglas no
   dependen del `id` ni de `.relevo-sale`: el valor no cambia en ningún cuadro, así que no hay nada
   que transicionar. */
#pantalla[data-esperando] > *{opacity:1;pointer-events:auto}
#pantalla[data-esperando]::after{content:none}
#pantalla[data-esperando] > .cabecera > .cabecera__pagina,
#pantalla[data-esperando] > .cuerpo,
.contenido[data-atenuada] > .cabecera > .cabecera__pagina,
.contenido[data-atenuada] > .cuerpo{
  opacity:.4;pointer-events:none;transition:opacity var(--dur-corta) var(--curva-salida);
}
#pantalla[data-esperando] > .espera-global--seccion,
.contenido[data-atenuada] > .espera-global--seccion{
  display:flex;animation:sof-aparece var(--dur-corta) var(--curva-salida) backwards;
}
@keyframes sof-aparece{from{opacity:0}}

/* La espera del legajo: el esqueleto de una lista, superpuesto, y la sección vieja se funde por
   debajo —el mismo mecanismo que Reasignar, más abajo: `opacity`/`visibility` y nunca `display`,
   porque un elemento fuera del árbol de render no anima—. Pausado mientras no se ve: el brillo y
   el isotipo son animaciones infinitas, y corriendo escondidas le piden cuadros al compositor. */
.legajo-seccion__carga{
  position:absolute;inset:0;overflow:hidden;opacity:0;visibility:hidden;pointer-events:none;
  padding-top:var(--esp-3);
  transition:opacity var(--dur-corta) var(--curva-salida),visibility 0s linear var(--dur-corta);
}
[data-carga-del-legajo] .espera,
[data-carga-del-legajo] .esqueleto__fila::after{animation-play-state:paused}
#legajo-seccion[data-esperando]::after{content:none}
#legajo-seccion[data-esperando] > [data-carga-del-legajo],
.legajo-seccion[data-atenuada] > [data-carga-del-legajo]{
  opacity:1;visibility:visible;transition:opacity var(--dur-corta) var(--curva-salida);
}
#legajo-seccion[data-esperando] > [data-carga-del-legajo] .espera,
#legajo-seccion[data-esperando] > [data-carga-del-legajo] .esqueleto__fila::after,
.legajo-seccion[data-atenuada] > [data-carga-del-legajo] .espera,
.legajo-seccion[data-atenuada] > [data-carga-del-legajo] .esqueleto__fila::after{animation-play-state:running}
/* `.legajo-seccion`, no cualquier `.relevo-sale`: escrito así apagaba también el contenido de la
   pantalla vieja del menú, que tiene que quedarse en .4 hasta fundirse. */
#legajo-seccion[data-esperando] > :not([data-carga-del-legajo]),
.legajo-seccion[data-atenuada] > :not([data-carga-del-legajo]){opacity:0}

/* ── El esqueleto en uso — elegir inspector en Reasignar (T117) ─────────────
   `.barra--elegible` pasa a HTMX (`_el_reparto.html`) y mientras la respuesta
   no llegó el panel de actas del inspector elegido muestra `.esqueleto` — la
   misma demora de 140 ms que ya usa el gesto 11 (`--demora-espera`), reusada
   y no reinventada, para que una respuesta rápida no parpadee.

   `hx-indicator="#reparto-actas"` es lo que hace que HTMX ponga `.htmx-request`
   en el contenedor y no sólo en el `<a>` que se tocó: acá lo que cambia de
   apariencia es el panel entero, no el enlace.

   🔴 **`opacity`/`visibility`, nunca `display` — medido, no la primera versión.**
   La primera versión ponía `hidden` (`display:none`) en el esqueleto y lo pasaba
   a `display:block` con una animación demorada. Contra el servidor real, con la
   respuesta demorada a propósito, **el esqueleto nunca aparecía**: un elemento en
   `display:none` está fuera del árbol de render, así que ningún `@keyframes` —ni
   siquiera su `animation-delay`— corre sobre él. El mismo problema tenía la otra
   mitad: esconder el contenido viejo con un `display:none` sin demora lo hacía
   desaparecer en el cuadro cero, antes de los 140 ms, así que una respuesta
   *rápida* sí destellaba — exactamente lo que la demora existe para evitar.

   Acá las dos mitades quedan superpuestas (`position:absolute` el esqueleto,
   `position:relative` el contenedor) y el toggle es `opacity`+`visibility`, que sí
   participan de una animación con demora — el mismo mecanismo que ya usa el
   isotipo superpuesto del botón (gesto 11): el elemento nunca deja de renderizarse,
   sólo deja de verse. */
#reparto-actas{position:relative}
[data-carga-del-reparto]{
  position:absolute;inset:0;opacity:0;visibility:hidden;pointer-events:none;
}
#reparto-actas.htmx-request [data-carga-del-reparto]{
  animation:sof-mostrar-esqueleto 0s linear var(--demora-espera) forwards;
}
@keyframes sof-mostrar-esqueleto{to{opacity:1;visibility:visible;pointer-events:auto}}
#reparto-actas.htmx-request > :not([data-carga-del-reparto]){
  animation:sof-esconder-el-reparto 0s linear var(--demora-espera) forwards;
}
@keyframes sof-esconder-el-reparto{to{opacity:0;visibility:hidden}}

/* ── Preferencia de movimiento reducido ─────────────────────────────────────
   No se apaga la información, se apaga el recorrido. */
@media (prefers-reduced-motion:reduce){
  .entra{opacity:1;transform:none}
  .entra-fundido{opacity:1}
  [data-vivo] .entra,[data-vivo] .entra-fundido{transition:none}
  .estado--intimada i{animation:none}
  .traspasa::after{animation:none;opacity:0}
  .aurora-lienzo{transition:none}
  /* 🔀 **La hoja funde, no corta** (`T169`; **R1**: se apaga el recorrido, no el fundido). Los dos
     extremos del recorrido valen lo mismo —la hoja no se desplaza ni se escala—, y lo único que
     cambia es la `opacity`, en `--dur-corta`, igual que el velo. Antes estaba en `transition:none`
     y la hoja pasaba de oculta a visible en un cuadro. */
  .hoja,[data-hoja-abierta] .hoja{
    transform:var(--hoja-abierta);
    transition:opacity var(--dur-corta) var(--curva-salida),visibility 0s linear var(--dur-corta);
  }
  [data-hoja-abierta] .hoja{
    transition:opacity var(--dur-corta) var(--curva-salida),visibility 0s;
  }
  /* 🔀 **Hoja y velo con la misma duración, en los dos sentidos** (`T181`, **H14**): el velo de cada
     hoja funde en `--dur-corta` igual que ella, abierto y cerrado (el estado abierto redeclara la
     `transition` con más especificidad, así que se apaga con la suya). */
  .velo,[data-hoja-abierta] .velo,
  .mas__velo,.pestanas[data-mas-abierto] .mas__velo,
  .pestanas:not([data-mas-listo]) .mas:target .mas__velo{transition:opacity var(--dur-corta) var(--curva-salida)}
  .filtros::after{
    transition:opacity var(--dur-corta) var(--curva-salida),visibility 0s linear var(--dur-corta);
  }
  .filtros[data-filtros-abiertos]::after{transition:opacity var(--dur-corta) var(--curva-salida),visibility 0s}
  .mas__item{transition:none}
  /* Los flotantes: sólo `opacity`, en `--dur-instante`, entrando y saliendo (`T181`, **H14**). */
  .desplegable__lista,.calendario__panel,
  .desplegable__lista:not([hidden]),.calendario__panel:not([hidden]){
    transform:none;
    transition:opacity var(--dur-instante) var(--curva-salida),
               display var(--dur-instante) var(--curva-salida) allow-discrete;
  }
  @starting-style{
    .desplegable__lista:not([hidden]),.calendario__panel:not([hidden]){transform:none}
  }
  /* 🔀 **Las hojas de navegación y de filtros funden, no cortan** (`T175`; **R1**, como `.hoja`).
     Quitar el recorrido no es quitar el fundido: los dos extremos valen lo mismo (`transform:none`) y
     lo único que cambia es la `opacity`, en `--dur-corta`. Se tocan el estado de reposo **y** el
     abierto (**H14**), que vuelve a declarar `transition` con más especificidad. En «Más» la capa
     (`.mas`) espera a que termine el fundido antes de esconderse. */
  .mas{transition:visibility 0s linear var(--dur-corta)}
  .mas__hoja{
    transform:none;opacity:0;
    transition:opacity var(--dur-corta) var(--curva-salida);
  }
  .pestanas[data-mas-abierto] .mas__hoja,
  .pestanas:not([data-mas-listo]) .mas:target .mas__hoja{
    opacity:1;transition:opacity var(--dur-corta) var(--curva-salida);
  }
  @container app (max-width:40rem){
    .filtros__hoja{
      transform:none;opacity:0;
      transition:opacity var(--dur-corta) var(--curva-salida),visibility 0s linear var(--dur-corta);
    }
    .filtros__hoja:target,.filtros[data-filtros-abiertos] .filtros__hoja{
      opacity:1;
      transition:opacity var(--dur-corta) var(--curva-salida),visibility 0s;
    }
  }
  /* El cielo cambia igual —el tema es información—, pero sin recorrido. 🔀 **El disco funde, no salta**
     (`T175`; **R1**): `portal.js` lo hace salir y entrar con `opacity` en `--dur-corta`, y el giro, que
     pasa a la propiedad `rotate` (el `translate` va por la suya, en el mismo orden que antes), cambia
     de golpe justo en la mitad, cuando el disco es invisible. Ningún valor intermedio de recorrido
     y ningún `transform` vivo. Nubes y estrellas ya están quietas para todo el mundo. */
  .cielo__disco{
    transform:none;translate:-50% -46%;rotate:var(--cielo-giro);
    transition:rotate 0s linear calc(var(--dur-corta) / 2);
  }
  /* 🔀 T170 · con `reduce` el tema cambia con un **fundido de opacidad corto** (`--dur-corta`):
     las manchas se cruzan, y los colores funden (`T181`: todos, por los tokens); el disco no gira (eso
     es recorrido). */
  .cielo__manchas{transition:opacity var(--dur-corta) var(--curva-salida)}
  /* `T181`: los mismos tokens, juntos, en `--dur-corta`: paneles y botones incluidos (antes cambiaban en
     0 ms) y el texto con ellos. */
  :root{transition-duration:var(--dur-corta);transition-timing-function:var(--curva-salida)}
  /* La transición entre páginas no se puede «no declarar» —el navegador ya tomó
     las instantáneas—, así que lo que se apaga es su recorrido. Sin `!important`:
     una regla de autor le gana a la hoja del navegador. */
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*){animation:none}
  /* El relevo de sección (T117b): la marca del menú salta a su lugar, la sección vieja se va en
     el mismo cuadro en que llega la nueva —`portal.js` ni siquiera la deja fundirse— y la nueva
     aparece sin subir. Lo que queda es la espera, quieta: informa. 🔀 **Sin squeeze** (`T178`, gesto
     13): `portal.js` no crea la animación con `reduce` (se lo pregunta en cada clic, así que activar
     o desactivar la preferencia con la página abierta cambia el comportamiento en el acto) y acá se
     apaga la `opacity` de las dos marcas (ningún estado la redeclara: el de «Más» abierto sólo cambia
     el color). */
  .nav__indicador,.pestanas__indicador{transition:none}
  :is(.contenido,.legajo-seccion)[data-cascada] [data-pieza],
  .relevo-sale{animation:none}
  #pantalla[data-esperando] > .espera-global--seccion{animation:none}
  #pantalla[data-esperando] > .cabecera > .cabecera__pagina,
  #pantalla[data-esperando] > .cuerpo,
  .legajo-seccion__carga,
  #legajo-seccion[data-esperando] > [data-carga-del-legajo]{transition:none}
  /* El botón sigue desplegando su sombra: eso informa. Lo que se apaga es el
     recorrido, no el estado. */
  .boton,.accion,.pestana,.nav a{transition:box-shadow var(--dur-corta) var(--curva-salida)}
  .boton:hover,.boton:active,.pestana:hover,.pestana:active,
  .nav a:hover,.nav a:active,
  .nav[data-indicador] a[aria-current="page"]:active{transform:none}
  /* T166: sin escala; el tinte de la presión se conserva (informa que el toque se registró). */
  .desplegable__control:active,.calendario__control:active,
  .casilla:active .casilla__marca{transform:none}
  .accion:hover,.accion:active{transform:translateX(-50%)}
  /* Presión al tocar (T117): el tinte de la fila y de `.barra--elegible` se
     conservan —informan que el toque entró— y se apaga el recorrido. La
     escala de la tarjeta de teléfono se apaga entera: un salto de escala sin
     transición se leería como un parpadeo, no como presión. */
  .grilla tbody tr:has(.enlace-fila:active),
  .barra--elegible:active{transition:none}
  .barra--elegible:active{transform:none}
  @container app (max-width:40rem){
    .grilla tbody tr:has(.enlace-fila){transition:background-size var(--dur-corta) var(--curva-salida)}
    .grilla tbody tr:has(.enlace-fila:active){transform:none;transition:none}
  }
  /* Gesto 11 · La espera **se conserva**: informa que el sistema está trabajando,
     y quitarla dejaría al usuario sin saber si su clic entró. Lo que se apaga es
     el recorrido del hilo, que pasa a ser una barra quieta del ancho completo. */
  [data-esperando]::after{animation:none;background-size:100% 100%;opacity:.5}
  [data-esperando] > *{transition:none}
  /* 🔒 Y el isotipo **no se va: se queda quieto**. Sacarlo dejaría al operador
     sin saber si su clic entró, que es el problema que T084 vino a resolver; lo
     que sobra con `reduce` es la vuelta, no la señal. Por eso `animation:none`
     y nunca `display:none` — la misma decisión que el hilo de acá arriba. */
  .espera{animation:none}
  /* La barra deja de correr y ocupa el riel entero: sigue diciendo «esto está en
     curso» sin nada moviéndose. El brillo del esqueleto sí se va del todo — es
     puro recorrido, y la forma de la lista ya informa por sí sola. */
  .barrido__avance{animation:none;left:0;width:100%}
  .esqueleto__fila::after{animation:none;content:none}
  /* Gesto 12 · La casilla **cambia igual** —el trazo está siempre, y es el que dice el estado—
     y se apaga el recorrido: el hilo no se desenrolla, el tilde aparece dibujado. Lo que se
     pierde es el viaje, no la información: el estado nunca depende sólo del color. */
  .casilla__aro,.casilla__tilde,.casilla__raya{transition:none}
  [data-sellado] .sello{animation:sellar-quieto var(--dur-sello) var(--curva-salida) forwards}
  @keyframes sellar-quieto{
    0%,88%{opacity:.96;transform:translate(-50%,-50%) rotate(-13deg)}
    100%  {opacity:0;  transform:translate(-50%,-50%) rotate(-13deg)}
  }
}

/* 13 · El squeeze de la marca — de dónde viene la pestaña y a cuál va.
   ────────────────────────────────────────────────────────────────────────────
   🔀 **La marca que se desliza (`T117b`) ahora se estira** (`T178`, 10/10/2026, pedido del dueño de
   producto: «una animación squeeze cuando pasa de una pestaña a otra»). No era un gesto con nombre:
   la marca viajaba entera con una transición de `transform`. Ahora cubre origen y destino y después se
   contrae sobre la nueva, así que afirma «esto viene de aquello y va a esto otro», sobre todo cuando se
   salta por encima de pestañas intermedias.

   Son **dos piezas**, el mismo gesto con los mismos tiempos: `.nav__indicador`, la píldora del menú
   lateral (escritorio y riel de tableta), que se estira en **alto**, y `.pestanas__indicador`, la raya
   de la barra del teléfono, que se estira en **ancho**.

   • **Los dos bordes** salen con tiempos distintos y terminan juntos: el que va **adelante** con
     `--curva-salida`, el de **atrás** con `--curva-cielo` (arranca y frena con peso, así el delantero
     le saca ventaja). Los dos en `--dur-media`. Sin rebote ni resorte: ninguna curva pasa de [0, 1] y
     cada borde avanza sin retroceder.
   • **Quién lo corre.** No es CSS: una transición tiene una sola curva por propiedad. Lo dispara
     `portal.js` (`estirar`, animaciones con `id` `sof-squeeze`) leyendo los tiempos y las curvas de
     los tokens. Acá no hay un `@keyframes` que lo haga y por eso este bloque no declara reglas: la
     pieza está en `componentes.css` y `portal.js`.
   • **Excepción acotada a R2.** Se estira con `width` / `height` del propio indicador y no con
     `scale`, que deformaría el borde de 1 px de la píldora y el radio de 3 px de la raya. Un indicador
     absoluto, `aria-hidden`, sin hijos y con `pointer-events:none` no participa del flujo: no reflowea
     nada. Prohibido animar `top`, `left`, `inset`, `margin` y `padding` en estas dos piezas; el
     desplazamiento sigue por `translate`. Ver `movimiento.md` § 2.
   • **Interrumpible (R4).** Un segundo clic parte de la posición y el tamaño vivos.
   • **Con `reduce`, la marca salta.** No se crea ninguna animación y la `transition` de las dos
     piezas queda en `none`: el estado (qué pestaña está activa) cambia igual. La primera colocación
     al cargar no viaja (`sin-viaje`). */

/* 14 · El reemplazo que cambia de alto — es la misma región con otro contenido, y por eso cambió su espacio.
   ────────────────────────────────────────────────────────────────────────────
   `T181` (decisión del dueño, 11/10/2026; ajustada al aprobar la galería, 12/10/2026). Lo que llega por
   un intercambio y ocupa otro alto —el resultado de un filtro, la sección del legajo, el padrón, la
   tarjeta de la nómina que se resuelve— cambiaba en un cuadro. Lo anima **una sola función**,
   `sofReemplazo` (`portal.js`), sobre los objetivos que llevan `data-reemplazo` en el marcado; el
   contrato está en la tarea y en `movimiento.md § 3.5`. Lo viejo (una copia inerte) se va primero, con
   el alto quieto; después lo nuevo entra y el alto va al suyo con `--curva-alto`: nunca se enciman.

   Acá está sólo lo que la función le pide al CSS, **por atributos y no por estilos en línea**: HTMX le
   devuelve a lo nuevo el `style` de la respuesta al asentar (`attributesToSettle`) y se llevaba un
   `overflow` o un `position` en línea en el cuadro siguiente (**G17**). `!important` porque mientras
   dura —un cuarto de segundo— tiene que ganarle a cualquier regla del componente.

   • `data-reemplazo-caja`: la caja que cambia de alto recorta lo que asoma. **`clip`, no `hidden`**:
     `hidden` crea un contenedor de desplazamiento y rompe los `sticky` de adentro (**A4**). Es la
     segunda excepción acotada a la **R2** (`movimiento.md § 2`): cambia el alto del contenedor, nunca el
     de una fila, y lo de adentro se maqueta una vez a su ancho final.
     Y `align-content:start`: con un alto fijo, una grilla reparte el sobrante entre sus filas (la zona de
     filtros a 390 bajaba su contenido 5 px mientras se achicaba) y la función mide lo de adentro.
   • `data-reemplazo-relativo`: si la caja no estaba posicionada, para colgar la copia.
   • `data-reemplazo-saliente`: la copia, y todo lo que lleva adentro, **no corre ninguna animación de
     CSS** (nace dentro de `.lista`, de una `.grilla` o de `[data-en-cascada]` y repetiría la cascada de
     la carga mientras se va) ni recibe el puntero. */
[data-reemplazo-caja]{overflow:clip !important;align-content:start !important}
[data-reemplazo-relativo]{position:relative !important}
[data-reemplazo-saliente],[data-reemplazo-saliente] *{animation:none !important;transition:none !important}
[data-reemplazo-saliente]{pointer-events:none}
