/* =========================================================
   Solved Design System — PANTALLA DE APP
   El fragmento de producto de una escena, construido en HTML.

   POR QUÉ EN HTML Y NO CON CAPTURAS
   Decisión del cliente: las pantallas que salen en la web se maquetan, no se
   capturan, porque así él ajusta el contenido sobre lo que ve —un dato, un
   estado, un nombre— sin volver a la app, abrir el módulo, dejarlo en el estado
   correcto y recortar. La captura envejece con cada release; esto no.

   Lo que NO cambia por maquetarlo: sigue siendo la app real. Mismo azul, misma
   tipografía, misma densidad, mismos datos de demo. Una pantalla inventada
   —columnas que no existen, un dashboard que la app no sabe pintar— es el
   anti-patrón que más credibilidad cuesta, y maquetar no es permiso para eso.
   La referencia viva es el tour de /incidencias/ (assets/tour/), que se hizo
   contra capturas reales: si algo no está allí, se comprueba antes de pintarlo.

   TIPOGRAFÍA
   Dentro de la pantalla manda Roboto, que es la fuente real de la app. Fuera
   manda Geist. Es el mismo criterio del tour: el fragmento no se rediseña para
   que pegue con la web, porque entonces deja de ser el producto.

   ESCALA
   La app se pinta a 0,8× de su densidad real. A tamaño natural, un fragmento de
   400px de ancho —una escena de 4 columnas— enseñaría la casilla y media
   descripción. A 0,8× entra la fila entera y el texto no baja de 10px, que es
   el suelo del componente.

   CÓMO SE RECORTA
   Por el borde, nunca encogiendo. El contenido lleva min-width mayor que la
   caja y sobra por la derecha y por abajo; el recorte lo hace el overflow de
   .scene__media o de .device__screen. Eso es lo que dice "esto continúa".

   Requiere ds/tokens.css y _ds/…/colors-and-type.css (los tokens del producto).
   Se usa dentro de .scene__media (ds/scene.css), con marco de dispositivo o sin
   él (ds/device.css).

     <div class="scene__media" data-crop="br">
       <div class="app app--tabla"> … </div>
     </div>
   ========================================================= */

/* `.bridge` va en el mismo selector, y no es un adorno: la ficha del ERP y el
   cable son HERMANOS de `.app`, no descendientes suyos, así que con los tokens
   declarados sólo en `.app` todo lo que hay fuera de la ventana se quedaba sin
   color —el punto de «Conectado» y los «Parte creado» en gris en vez de verde,
   las separaciones del registro invisibles, los códigos sin tinta y, sobre todo,
   la bolita del cable con `stroke:none`, porque una variable que no existe deja
   la declaración inválida y el valor heredado es el `fill="none"` del <svg>—.
   Se ve en el marcado de aquí arriba: `.bridge > .bridge__win > .device > … >
   .app`, y al lado `.bridge__wire` y `.bridge__card`. Detectado el 8 de
   septiembre de 2026 al poner el conector y no aparecer. */
.app,
.bridge,
.app__asigna,
.app__raiz,
/* `.app__conv` es la cuarta pieza que vive FUERA de `.app` —el cajón del chat de
   /incidencias/, panel suelto y sin marco de aparato— y le pasa lo mismo que le
   pasó a `.bridge` el 8 de septiembre: sin esta línea,
   `background:var(--app-blue)` es una declaración inválida y la burbuja del
   mensaje propio salía TRANSPARENTE con la letra en blanco, o sea invisible.
   Cuarta vez que aparece el mismo fallo: **si una pieza de producto no cuelga de
   `.app`, entra aquí**. Y la quinta, 29 sep 2026: `.regpush__notif`
   (ds/scene.css), la notificación de `/gestion-de-activos/` — vive fuera de
   `.app` a propósito, porque tapa la barra superior de la app al caer, y sin
   esta línea su icono, su borde y el círculo del toque salían transparentes. */
.regpush__notif,
.app__conv,
.app__comparte,
/* Sexta vez (5 oct 2026): `.app__mcard` vive siempre dentro de `.app__mlist`
   dentro de `.app`, así que nunca necesitó tokens propios — hasta que
   /software-gmao/ lo usó SUELTO como ficha compacta sobre una foto, sin
   `.app` alrededor. Entra aquí por el mismo motivo de siempre: si una pieza
   de producto no cuelga de `.app`, entra en esta lista. Como son variables
   CSS, heredan solas a cualquier `.app__item`/`.app__opt`/`.app__kpi` que
   se meta dentro de la ficha, sin añadirlos uno a uno. */
.app__mcard{
  /* Tokens del producto, no de la web. Los valores de reserva son por si la
     pantalla se monta en una página que no cargue el paquete de _ds. */
  --app-blue:var(--blue,#1E6BFF);
  --app-blue-deep:var(--blue-700,#1450C7);
  --app-blue-wash:var(--blue-50,#EAF1FF);
  --app-canvas:var(--ink-50,#F4F6FB);
  --app-line:var(--ink-100,#E6EAF2);
  --app-line-2:var(--ink-200,#C7CFDD);
  --app-mute:var(--ink-400,#8B95A7);
  --app-text-2:var(--ink-600,#4A5468);
  --app-text:var(--ink-800,#1B2230);
  --app-ok:var(--ok,#2F9E44);
  --app-warn:var(--warn,#F59F00);
  --app-crit:var(--crit,#E03131);
  --app-accion:var(--accion,#F2700F);
  /* El morado de Registros estaba escrito a pelo en cinco sitios del bloque de
     color del dashboard. Al usarlo también en las barras se le da alias, como
     tienen los demás, y el valor de reserva es el token del sistema. */
  --app-registro:var(--module-registros,#7C3AED);
}

/* Y la caja de la pantalla, que es sólo de `.app`: el puente hereda los tokens,
   no el lienzo ni la maquetación. Compartir también esto le daba a `.bridge` el
   fondo `--app-canvas` a toda la escena —un panel azulado detrás de la
   composición, que es exactamente lo que no tiene que haber— además de un
   `display:flex` y una tipografía que no le tocan. */
.app{
  position:relative;
  width:100%;
  height:100%;
  overflow:hidden;
  display:flex;
  flex-direction:column;
  background:var(--app-canvas);
  color:var(--app-text);
  font-family:Roboto,'Helvetica Neue',Arial,sans-serif;
  font-weight:400;
  text-align:left;
  /* Las cifras de un producto industrial van siempre tabulares. */
  font-variant-numeric:tabular-nums;
}
/* Dentro de un marco de dispositivo la pantalla ya pone su radio y su recorte;
   la app no trae ni borde ni esquinas propias (ds/device.css la posiciona en
   absoluto sobre la pantalla). Fuera del marco sí las trae: es el canto del
   fragmento suelto. */
.scene__media>.app{ border-radius:inherit }
.device__screen .app{ border-radius:0 }

/* ds/scene.css estira al 100 % cualquier <img> que haya dentro del fragmento
   —la regla se escribió cuando el fragmento era una captura entera—. Las
   imágenes de dentro de la app tienen su propio tamaño, así que aquí se
   desactiva con una especificidad que gane. */
.app img{ width:auto; height:auto; max-width:none }
.app .app__thumb{ width:30px; height:30px }
.app .app__logo{ height:17px; width:auto }
.app .app__co img{ width:38px; height:38px }

/* ---------------------------------------------------------
   BARRA SUPERIOR — la de la app, que es azul y llena el ancho
   No confundir con el cromo de navegador que el componente prohíbe: eso es la
   ventana; esto es el producto.
   --------------------------------------------------------- */
.app__top{
  flex:0 0 auto;
  height:42px;
  display:flex;
  align-items:center;
  gap:11px;
  padding:0 14px;
  background:var(--app-blue);
  color:#fff;
}
.app__logo{ height:17px; width:auto; display:block; filter:brightness(0) invert(1) }
.app__vbar{ width:1px; height:18px; background:rgba(255,255,255,.28) }
.app__topttl{ font-size:13px; font-weight:500 }
.app__spacer{ flex:1 }
.app__ic{ width:26px; height:26px; display:grid; place-items:center; color:rgba(255,255,255,.92) }
.app__ic svg{ width:15px; height:15px; display:block }
.app__me{
  width:24px; height:24px; border-radius:50%;
  background:rgba(255,255,255,.2);
  display:grid; place-items:center;
  font-size:11px; font-weight:500; color:#fff;
}

/* ---------------------------------------------------------
   CUERPO — sidebar + zona de trabajo
   --------------------------------------------------------- */
.app__body{ flex:1; min-height:0; display:flex }

.app__side{
  flex:0 0 132px;
  background:#fff;
  border-right:1px solid var(--app-line);
  padding:11px 8px;
  display:flex;
  flex-direction:column;
  gap:2px;
}
/* El bloque de empresa y el paso del menú van justos a propósito: en un
   fragmento, el ítem activo —el que dice en qué pantalla estamos— tiene que
   caer por encima del corte, y cada píxel de más aquí lo empuja fuera. */
.app__co{
  display:flex; flex-direction:column; align-items:center; gap:5px;
  padding:2px 6px 8px;
}
.app__co img{
  width:38px; height:38px; border-radius:8px;
  border:1px solid var(--app-line); object-fit:cover; display:block;
}
.app__co span{ font-size:10px; font-weight:500; color:var(--app-text); text-align:center }
.app__nav{
  display:flex; align-items:center; gap:9px;
  padding:5px 9px; border-radius:6px;
  font-size:11.5px; color:var(--app-text-2);
}
.app__nav svg{ width:15px; height:15px; flex:0 0 auto; color:var(--app-mute) }
.app__nav--on{ background:var(--app-blue-wash); color:var(--app-blue-deep); font-weight:500 }
.app__nav--on svg{ color:var(--app-blue) }

/* — MENÚ CONTRAÍDO (.app__side--min) —
   El estado de la app cuando se mira el tablero: quedan los iconos y el ítem
   activo se sigue reconociendo por su fondo. Gana 88px para la zona de trabajo,
   que en una escena recortada es medio panel más a la vista.

   Los rótulos NO se borran del marcado: se tapan. Si se borraran, se caerían
   del catálogo de traducción —que lee el HTML— y el día que alguien despliegue
   el menú volverían en español en los cinco idiomas. `font-size:0` en el padre
   y su tamaño de vuelta en el hijo es la forma de esconder un nodo de texto
   suelto, que no se puede seleccionar de otra manera. */
.app__side--min{ flex-basis:44px; padding-inline:6px }
.app__side--min .app__nav{
  justify-content:center;
  gap:0; padding-inline:0;
  font-size:0;
}
.app__side--min .app__nav svg{ width:16px; height:16px }
.app__side--min .app__co{ padding-inline:0 }
.app__side--min .app__co img{ width:30px; height:30px }
/* El nombre de la empresa se va con los rótulos: sin él, el logotipo hace de
   marca y la columna se queda a un solo ancho. */
.app__side--min .app__co span{ display:none }

.app__main{
  flex:1; min-width:0;
  padding:13px 16px;
  display:flex; flex-direction:column; gap:11px;
}

/* ---------------------------------------------------------
   CABECERA DE LISTADO — segmentos de vista, de estado y el botón de añadir
   --------------------------------------------------------- */
.app__listtop{ display:flex; align-items:center; gap:10px; flex:0 0 auto }
/* Nada de la cabecera encoge: si no cabe, se sale por el borde y lo corta el
   marco, que es como se recorta todo aquí. */
.app__listtop>*{ flex:0 0 auto }
.app__seg{
  display:inline-flex; align-items:center; gap:3px;
  background:#fff; border:1px solid var(--app-line);
  border-radius:8px; padding:3px;
}
.app__segb{
  display:inline-flex; align-items:center; gap:5px;
  padding:5px 10px; border-radius:5px;
  font-size:10.5px; font-weight:500; color:var(--app-text-2);
  white-space:nowrap;
}
.app__segb svg{ width:12px; height:12px }
.app__segb--on{ background:var(--app-blue); color:#fff }
.app__segb--stat{ background:var(--app-blue-wash); color:var(--app-blue-deep) }
.app__cnt{
  font-size:10px; padding:1px 6px; border-radius:999px;
  background:var(--app-line); color:var(--app-text-2);
}
.app__segb--stat .app__cnt{ background:var(--app-blue); color:#fff }
.app__add{
  display:inline-flex; align-items:center; gap:6px;
  padding:6px 12px; border-radius:7px;
  background:#fff; border:1px solid var(--blue-300,#8EB1FF);
  color:var(--app-blue-deep); font-size:11px; font-weight:500;
  white-space:nowrap;
}
.app__add svg{ width:13px; height:13px }

/* ---------------------------------------------------------
   TARJETA DE TABLA
   El ancho natural va por delante del hueco: la tabla se sale por la derecha y
   la corta el marco. Nunca se quitan columnas para que quepa —eso sería otra
   app—, se enseñan menos.
   --------------------------------------------------------- */
.app__card{
  flex:1; min-height:0;
  background:#fff;
  border:1px solid var(--app-line);
  border-radius:11px;
  overflow:hidden;
}
.app__tbar{
  display:flex; align-items:center; gap:10px;
  padding:8px 12px; border-bottom:1px solid var(--app-line);
  font-size:10.5px; color:var(--app-text-2);
}
.app__tbar svg{ width:13px; height:13px; color:var(--app-mute) }
.app__range{ color:var(--app-blue-deep); font-weight:500 }

/* ---------------------------------------------------------
   EL KANBAN DE ACCIONES — comprobado en demo.trysolved.com/actions
   (Ultimate Demo 6). Primera versión (15 sep 2026): cabeceras como una
   ficha con fondo lavado y tarjetas sueltas con avatar del responsable.
   Repasado el 23 de septiembre de 2026 contra la pantalla real —el cliente
   avisó de que no se parecía— y las dos cosas estaban mal:

   1. LA CABECERA es una barra de color SÓLIDO a todo el ancho de la
      columna —no una ficha lavada—, con el contador en una píldora blanca
      a la derecha. Los tres colores son los del propio Kanban (celeste,
      amarillo, menta), no los tokens de prioridad ni el azul del sistema:
      es una paleta que sólo existe aquí.
   2. LA TARJETA no lleva avatar del responsable —el tablero real no lo
      enseña—. Lleva un icono cuadrado a la izquierda (el que trae cada
      tarjeta cuando no hay foto propia), el código y la fecha en la misma
      fila, y la descripción debajo. Las tarjetas de una columna comparten
      una sola superficie blanca con las filas separadas por hairline, no
      tarjetas sueltas con su propio marco.

   El código sigue siendo el de la incidencia que originó la tarea, sin
   segundo código de acción (`UTD26_AC_…`), que es la simplificación de
   origen: introducir un formato nuevo en una sola pantalla no compensa.
   --------------------------------------------------------- */
.app__kan{ display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:9px; flex:1; min-height:0 }
.app__kancol{ display:flex; flex-direction:column; min-width:0 }
.app__kancolh{
  display:flex; align-items:center; gap:6px;
  padding:7px 10px; border-radius:7px 7px 0 0;
  font-size:10px; font-weight:600; color:var(--app-text);
}
.app__kancolh .app__cnt{
  margin-left:auto;
  background:#fff; color:inherit;
  border-radius:999px; padding:1px 7px;
  font-size:9px; font-weight:700;
}
.app__kancol--abierto .app__kancolh{ background:#74C0FC }
.app__kancol--revision .app__kancolh{ background:#FFD43B }
.app__kancol--cerrado .app__kancolh{ background:#63E6BE }
.app__kanbody{
  flex:1; min-height:0; overflow:hidden;
  display:flex; flex-direction:column;
  background:#fff; border:1px solid var(--app-line); border-top:0;
  border-radius:0 0 7px 7px;
}
.app__kancard{
  display:flex; align-items:flex-start; gap:8px;
  padding:8px 9px;
  border-bottom:1px solid var(--app-line);
  opacity:0;
  animation-name:app-msg; animation-duration:13s;
  animation-timing-function:cubic-bezier(.16,1,.3,1); animation-iteration-count:infinite;
  animation-play-state:var(--play,running);
}
.app__kancard:last-child{ border-bottom:0 }
/* El icono por defecto de una tarjeta sin foto propia. Azul fijo: en la
   pantalla real no cambia con la columna, es el estado "sin adjunto". */
.app__kanico{
  flex:none; width:22px; height:22px; border-radius:6px;
  display:grid; place-items:center;
  background:var(--app-blue-wash); color:var(--app-blue);
}
.app__kanico svg{ width:12px; height:12px }
.app__kanmain{ min-width:0; flex:1; display:flex; flex-direction:column; gap:2px }
.app__kantop{ display:flex; align-items:baseline; justify-content:space-between; gap:6px }
.app__kandate{ flex:none; font-size:8.5px; color:var(--app-mute); white-space:nowrap }
/* La tarjeta lleva SU PROPIO código —el de la incidencia que la originó—,
   igual que el resto de tarjetas del módulo: es el mismo `.app__code`, pero
   aquí en el naranja de Acciones y no en el azul de Incidencias, porque
   dentro del tablero lo que se lee es la acción, no su origen. */
.app__kancard .app__code{ color:var(--app-accion); font-size:10px }
.app__kantxt{ margin:0; font-size:10px; line-height:1.3; color:var(--app-text) }
/* Sin regla de móvil a propósito: el resto de fragmentos del módulo (la
   tabla, el calendario de /auditorias/) se recortan por el borde en vez de
   apilarse, y tres columnas que pasan a una fila entera dentro de una caja
   1:1 se recortarían igual por abajo — no se gana nada cambiando el patrón
   aquí. */

/* ---------------------------------------------------------
   COMPARTIR UN CAMPO CON UN PROVEEDOR O UN CLIENTE
   Comprobado en demo.trysolved.com/incidents (Ultimate Demo 6) el 23 de
   septiembre de 2026, en una incidencia de la plantilla «Reclamación»: NO
   existe un botón «enviar la tarea al proveedor» que mande la acción entera
   con su propio protocolo, que es lo que la tarjeta anterior de esta
   sección afirmaba sin haberlo comprobado. Lo que existe es más concreto:
   los campos Proveedor y Cliente llevan un icono de enlace al lado. Al
   compartirlo, ese campo pasa a mostrar «Compartido» con un icono, y su
   tooltip dice literalmente «Este campo se compartió con un usuario externo
   mediante un enlace: su contenido puede haber sido rellenado desde fuera
   de la plataforma». Es decir: se comparte el CAMPO con un enlace, no la
   tarea con un protocolo — quien lo recibe lo rellena sin entrar en Solved
   y sin ver el resto de la incidencia.

   La animación cuenta ese gesto en un solo campo (Proveedor), con el mismo
   reloj de 13 s que ya usaba esta tarjeta: el valor entra, el icono de
   compartir se pulsa y la píldora «Compartido» aparece. Reutiliza `.res-pill`
   —el mismo componente del aviso de una línea— en vez de inventar uno.
   --------------------------------------------------------- */
.app__comparte{
  width:100%;
  background:#fff;
  border:1px solid var(--app-line);
  border-radius:11px;
  box-shadow:0 16px 32px rgba(50,50,93,.12);
  padding:12px 13px 13px;
  display:flex; flex-direction:column; gap:11px;
  font-family:Roboto,'Helvetica Neue',Arial,sans-serif;
  color:var(--app-text);
  font-variant-numeric:tabular-nums;
  text-align:left;
}
.app__comparte .app__campo{ display:flex; flex-direction:column; gap:4px }
.app__comparte .app__campok{ font-size:9.5px; color:var(--app-mute) }
.app__comparte .app__campov--pick{
  display:grid; grid-template-columns:minmax(0,1fr) auto;
  align-items:center; gap:8px;
  padding:6px 9px; border:1px solid var(--app-line); border-radius:7px;
  min-height:15px;
}
.app__comparte .app__campoph,
.app__comparte .app__campoval{
  grid-column:1; grid-row:1; min-width:0; overflow:hidden;
  display:flex; align-items:center; gap:6px; font-size:10.5px;
}
.app__comparte .app__campoph{ color:var(--app-mute) }
.app__comparte .app__campoval{ opacity:0 }
.app__comparte .app__campoph{ animation:app-comp-ph 13s ease-out infinite; animation-play-state:var(--play,running); animation-iteration-count:var(--iter,infinite); animation-fill-mode:forwards; }
.app__comparte .app__campoval{ animation:app-comp-val 13s ease-out infinite; animation-play-state:var(--play,running); animation-iteration-count:var(--iter,infinite); animation-fill-mode:forwards; }
@keyframes app-comp-ph{  0%,15%{opacity:1} 18%,100%{opacity:0} }
@keyframes app-comp-val{ 0%,18%{opacity:0} 21%,100%{opacity:1} }
/* El botón de compartir: circular, como el icono de enlace de la pantalla
   real. Se enciende en el 40 % del reloj, que es cuando la píldora de abajo
   ya está a punto de aparecer — primero el gesto de compartir, luego la
   confirmación. */
.app__comparte__share{
  flex:none; width:24px; height:24px; border-radius:50%;
  display:grid; place-items:center; border:0;
  background:#fff; color:var(--app-mute);
  box-shadow:inset 0 0 0 1px var(--app-line);
  animation:app-comp-share 13s ease-out infinite;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
}
.app__comparte__share svg{ width:12px; height:12px }
@keyframes app-comp-share{
  0%,38%   { background:#fff; color:var(--app-mute); box-shadow:inset 0 0 0 1px var(--app-line) }
  41%,50%  { background:var(--app-blue); color:#fff; box-shadow:0 0 0 5px var(--app-blue-wash) }
  53%,100% { background:var(--app-blue); color:#fff; box-shadow:inset 0 0 0 1px var(--app-blue) }
}
/* La píldora reutiliza `.res-pill`, pero aquí vive dentro de la tarjeta y no
   flotando sobre una foto: sin `position:absolute`. */
.app__comparte .res-pill{
  position:static; align-self:flex-start;
  opacity:0; translate:0 6px;
  animation:app-comp-pill 13s ease-out infinite;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
}
@keyframes app-comp-pill{ 0%,50%{opacity:0; translate:0 6px} 56%,100%{opacity:1; translate:0 0} }

.app__table{ width:100%; border-collapse:collapse; min-width:var(--app-table-w,620px) }
.app__table th{
  text-align:left; padding:8px 11px;
  background:var(--app-canvas); border-bottom:1px solid var(--app-line);
  font-size:10px; font-weight:500; color:var(--app-text-2); white-space:nowrap;
}
.app__table td{
  padding:8px 11px; border-bottom:1px solid var(--app-line);
  font-size:11px; color:var(--app-text-2); white-space:nowrap;
}

.app__desc{ display:flex; align-items:center; gap:9px }
.app__thumb{
  width:30px; height:30px; border-radius:6px; object-fit:cover; flex:0 0 auto;
  box-shadow:inset 0 0 0 1px rgba(11,18,32,.06);
}
.app__thumb--none{
  background:linear-gradient(135deg,#F5C542,var(--app-warn));
  display:grid; place-items:center; color:rgba(255,255,255,.92);
}
.app__thumb--none svg{ width:15px; height:15px }
.app__code{ font-size:10.5px; font-weight:500; color:var(--app-blue) }
.app__dtxt{ font-size:10.5px; color:var(--app-text-2); margin-top:1px }

.app__user{ display:inline-flex; align-items:center; gap:7px }
.app__ava{
  width:20px; height:20px; border-radius:50%; flex:0 0 auto;
  display:grid; place-items:center;
  font-size:10px; font-weight:500; color:#fff;
}
.app__tag{
  display:inline-flex; padding:2px 9px; border-radius:999px;
  font-size:10px; font-weight:500;
}
.app__tag--alta{ background:#FBD5D5; color:#B02020 }
.app__tag--media{ background:var(--prio-media-bg,#FCEFB8); color:var(--prio-media-fg,#8A6A12) }
.app__tag--curso{ background:var(--accion-bg,#FCEBD9); color:var(--app-accion) }
.app__tag--abierta{ background:var(--app-blue-wash); color:var(--app-blue-deep) }
.app__tag--hecha{ background:#E4F6EA; color:#12813B }
.app__num{ color:var(--app-blue-deep); font-size:11px }
.app__num--crit{ color:var(--app-crit) }
.app__chk{
  display:inline-block; width:13px; height:13px;
  border:1.5px solid var(--app-line-2); border-radius:3px;
}

/* ---------------------------------------------------------
   FRAGMENTO SIN MARCO
   Una escena sin aparato no enseña la ventana entera: enseña la zona de
   trabajo. Sin barra azul y sin sidebar, que a 400px se comerían el fragmento
   y no son lo que la tarjeta está contando.
   --------------------------------------------------------- */
.app--fragmento{ padding:13px 15px; display:flex; flex-direction:column; gap:10px }

/* ---------------------------------------------------------
   MÓVIL — el operario reporta de pie, delante de la máquina
   La tabla de ocho columnas no existe aquí: la app de móvil apila. Es la misma
   regla del sistema —antes que reducir hasta el ruido gris, se enseña menos—,
   sólo que en la app ya viene resuelta.
   --------------------------------------------------------- */
.app--movil{ background:var(--app-canvas) }
.app--movil .app__top{
  /* Hueco para la isla dinámica: sin él el título queda debajo del recorte. */
  height:auto; padding:26px 12px 9px; gap:8px;
}
.app--movil .app__topttl{ font-size:12.5px }
.app__mfilters{
  flex:0 0 auto; display:flex; gap:5px;
  padding:9px 10px 7px; background:#fff; border-bottom:1px solid var(--app-line);
}
.app__mchip{
  padding:4px 9px; border-radius:999px;
  font-size:10px; color:var(--app-text-2); background:var(--app-canvas);
  white-space:nowrap;
}
.app__mchip--on{ background:var(--app-blue-wash); color:var(--app-blue-deep); font-weight:500 }
.app__mlist{ list-style:none; margin:0; padding:8px; display:flex; flex-direction:column; gap:7px }
.app__mcard{
  display:flex; gap:8px; align-items:flex-start;
  background:#fff; border:1px solid var(--app-line); border-radius:9px;
  padding:8px;
}
.app--movil .app__mcard .app__thumb{ width:28px; height:28px }
.app__mtxt{ min-width:0 }
.app__mdesc{ font-size:11px; line-height:1.25; color:var(--app-text); margin-top:2px }
.app__mmeta{
  display:flex; align-items:center; gap:6px; flex-wrap:wrap;
  margin-top:5px; font-size:10px; color:var(--app-mute);
}
/* Botón de añadir flotante: en la app de planta es la afordancia principal, y
   sin él la pantalla parece un visor y no una herramienta de reporte. */
.app__fab{
  position:absolute; right:11px; bottom:13px;
  width:34px; height:34px; border-radius:50%;
  background:var(--app-blue); color:#fff;
  display:grid; place-items:center;
  box-shadow:0 4px 12px rgba(30,107,255,.36);
}
.app__fab svg{ width:17px; height:17px }

/* ---------------------------------------------------------
   REGISTRO CUMPLIMENTADO — la ronda firmada en la tablet de línea
   Un registro no es un listado: lo que demuestra el módulo es el papel relleno,
   con sus OK, su KO y su firma. Un listado de registros no demuestra nada que
   no demuestre ya la tabla de incidencias.
   --------------------------------------------------------- */
/* El registro no se sale por el costado: cortar la columna de OK/KO sería
   cortar justo lo que la tarjeta demuestra. Se sale por abajo, por el último
   punto de la ronda.
   Por eso lleva ancho fijo en vez de estirarse: el aparato sí se sale por la
   derecha —lo corta el borde de la escena, que es lo que dice "esto continúa"—
   y el contenido tiene que caber en la parte que queda a la vista. */
.app__rec{
  flex:1; min-height:0; width:var(--app-rec-w,auto);
  padding:12px 14px; display:flex; flex-direction:column; gap:9px;
}
/* Tablet de línea. El ancho va en porcentaje, no en píxeles, porque lo que
   fija es la parte de la pantalla que queda a la vista: con el encuadre
   dev-br el aparato entra por el 13 % y mide el 104 %, así que se ve el 84 %
   escaso de su ancho. En porcentaje eso sigue valiendo cuando la escena pasa a
   ancho completo en tablet; en píxeles dejaría medio lienzo vacío. */
.app--tablet{ --app-rec-w:84% }
.app__rechead{
  display:flex; align-items:center; gap:9px;
  background:#fff; border:1px solid var(--app-line); border-radius:9px;
  padding:9px 11px;
}
.app__recico{
  width:28px; height:28px; border-radius:7px; flex:0 0 auto;
  background:var(--app-blue-wash); color:var(--app-blue);
  display:grid; place-items:center;
}
.app__recico svg{ width:15px; height:15px }
.app__rect{ font-size:12px; font-weight:500; color:var(--app-text) }
.app__recs{ font-size:10px; color:var(--app-text-2); margin-top:1px }
.app__recprog{
  margin-left:auto; font-size:10px; font-weight:500;
  color:var(--app-ok); white-space:nowrap;
}
/* La lista no encoge. Si los puntos de la ronda no caben, se salen por abajo y
   los corta el marco; encogerlos dejaría medio punto a la vista y el registro
   parecería un recuadro con dos líneas. */
.app__items{
  flex:0 0 auto;
  background:#fff; border:1px solid var(--app-line); border-radius:9px;
  overflow:hidden;
}
.app__item{
  display:flex; align-items:center; gap:9px;
  padding:8px 11px; border-bottom:1px solid var(--app-line);
  font-size:11px; color:var(--app-text);
}
.app__itemq{ flex:1; min-width:0 }
/* — LAS DOS OPCIONES DE UN CONTROL (8 sep 2026) —
   Antes había UNA píldora con la respuesta ya dada. Ahora está el control como
   está en la app: **sus dos botones, OK y KO**, y el que se pulsa se rellena.
   El cambio no es cosmético — es la diferencia entre enseñar un resultado y
   enseñar cómo se responde, que es lo que la tarjeta promete.

   Los dos estados son los del producto: sin pulsar, fondo blanco con el borde y
   la letra del color; pulsado, el color de fondo y la letra en blanco. El color
   vive en `--c` y no en cada regla porque el fotograma tiene que interpolar
   entre los dos estados y necesita el mismo valor a los dos lados.

   La píldora sólo existe en esta pantalla —comprobado en las 703 páginas—, así
   que rehacerla no toca nada más. */
.app__opts{ display:inline-flex; gap:5px; flex:0 0 auto }
.app__opt{
  --c:#12813B;
  display:inline-flex; align-items:center; justify-content:center;
  min-width:28px; padding:2px 8px;
  border:1px solid var(--c); border-radius:999px;
  background:#fff; color:var(--c);
  font-size:10px; font-weight:500; white-space:nowrap;
}
.app__opt--ko{ --c:#B02020 }
/* El estado de reposo del que va a quedar pulsado. Lo pinta el keyframe durante
   la animación; esto es lo que se ve si el navegador no la ejecuta. */
.app__opt--on{ background:var(--c); color:#fff }
/* Un KO abre incidencia sola: es el argumento entero del módulo, así que el
   enlace se ve, no se cuenta. */
.app__link{
  font-size:10px; color:var(--app-blue); font-weight:500; white-space:nowrap;
}
.app__sign{
  display:flex; align-items:center; gap:9px;
  background:#fff; border:1px solid var(--app-line); border-radius:9px;
  padding:9px 11px; font-size:10.5px; color:var(--app-text-2);
}
.app__signimg{ height:22px; width:auto; display:block; opacity:.85 }

/* ---------------------------------------------------------
   DASHBOARD — el escritorio de calidad o dirección
   Cifras y una serie, que es lo que la app pinta. Ni anillos, ni mapas de
   calor, ni gráficos que el producto no sabe hacer.
   --------------------------------------------------------- */
.app__dash{ display:flex; flex-direction:column; gap:11px; min-width:var(--app-dash-w,560px) }
.app__kpis{ display:grid; grid-template-columns:repeat(4,1fr); gap:10px }
.app__kpi{
  background:#fff; border:1px solid var(--app-line); border-radius:9px;
  padding:10px 12px;
}
.app__kpil{ font-size:10px; color:var(--app-text-2) }
.app__kpiv{
  font-size:22px; font-weight:400; color:var(--app-text);
  line-height:1.1; margin-top:5px;
}
.app__kpiv small{ font-size:11px; color:var(--app-text-2); margin-left:2px }
.app__delta{ font-size:10px; margin-top:3px }
.app__delta--ok{ color:var(--app-ok) }
.app__delta--crit{ color:var(--app-crit) }

.app__panels{ display:grid; grid-template-columns:1.35fr 1fr; gap:10px }
.app__panel{
  background:#fff; border:1px solid var(--app-line); border-radius:9px;
  padding:10px 12px 12px;
}
.app__panelt{ font-size:11px; font-weight:500; color:var(--app-text) }
.app__panels{ align-items:start }
/* El panel de listado ocupa el ancho del dashboard y la tabla se conforma con
   él: aquí no hay que forzar ancho natural, el recorte ya lo dan la ventana por
   la derecha y el borde de la escena por abajo. */
.app__panel--list{ padding-bottom:0 }
.app__panel--list .app__table{ min-width:0; margin-top:6px }
.app__panel--list .app__table td{ padding:7px 0 }
.app__panel--list .app__table tr:last-child td{ border-bottom:0 }

/* Barras del mes. Se dibujan con CSS y no con SVG porque son seis rectángulos
   y una línea base: un SVG aquí sólo añade un sistema de coordenadas más. */
/* Las columnas se estiran a la altura de la caja —no se ajustan al contenido—
   porque la altura de cada barra va en porcentaje y necesita una referencia
   definida. Con align-items:flex-end el porcentaje no tendría contra qué medir. */
.app__bars{ display:flex; align-items:stretch; gap:10px; height:82px; margin-top:12px }
.app__bar{ flex:1; display:flex; flex-direction:column; justify-content:flex-end; gap:5px }
/* Sin fondo propio: lo que pinta la barra son sus tramos (.app__bseg), más
   abajo. El radio se queda aquí y lo recorta el overflow del contenedor. */
.app__barv{ border-radius:3px 3px 0 0 }
.app__barl{ font-size:10px; color:var(--app-mute); text-align:center }

.app__breaks{ margin-top:10px; display:flex; flex-direction:column; gap:8px }
.app__break{ font-size:10px; color:var(--app-text-2) }
.app__breakh{ display:flex; justify-content:space-between; gap:8px }
.app__track{
  height:5px; border-radius:999px; background:var(--app-line);
  margin-top:4px; overflow:hidden;
}
.app__fill{ height:100%; border-radius:999px; background:var(--app-blue) }

/* ---------------------------------------------------------
   PANTALLAS PEQUEÑAS
   Por debajo de tablet la escena pasa a ancho completo y el aparato encoge con
   ella; el texto de la app no encoge —va en px, y el suelo son 10— así que lo
   que se hace es enseñar menos: fuera la miniatura y fuera las columnas de la
   derecha. Es la regla de siempre, aplicada al fragmento.
   --------------------------------------------------------- */
@media (max-width:600px){
  .app--movil .app__thumb{ display:none }
  .app--movil .app__mfilters{ padding:7px 8px 6px }
}

/* =========================================================
   LA PANTALLA, MÁS PEQUEÑA EN PANTALLAS ESTRECHAS
   La pantalla de producto está maquetada en píxeles fijos —columnas, filas,
   avatares— y vive dentro de una caja con `overflow:hidden`. Eso es lo que
   quiere el sistema: la escena enseña un FRAGMENTO, y el recorte es el gesto
   que dice «esto continúa».

   Lo que no quiere es que el fragmento sea otro en cada tamaño. Medido el 3 de
   septiembre de 2026 sobre la home: en escritorio se ve entre el 68 % y el
   100 % de cada interfaz; a 390px, el 55 %. O sea que en el móvil el recorte
   deja de ser un gesto y se convierte en una frase cortada por la mitad.

   Se corrige con `zoom` y no con `transform:scale`: zoom recalcula la caja, así
   que la pantalla sigue llenando su hueco exacto sin tener que agrandarla
   primero para encogerla después. Los factores salen de la proporción medida
   entre el hueco del móvil y el de escritorio (353/476 ≈ .74); se quedan algo
   por encima para no bajar el cuerpo del texto de la interfaz de 13px a menos
   de 10, que es donde deja de leerse.

   La maqueta de teléfono (.app--movil) no entra: ya está dibujada a tamaño de
   teléfono, y encogerla la haría ilegible sin ganar nada de encuadre.
   ========================================================= */
@media (max-width:900px){
  .app:not(.app--movil){ zoom:.86 }
}
@media (max-width:600px){
  .app:not(.app--movil){ zoom:.78 }
}

/* =========================================================
   COLOR EN EL DASHBOARD
   Hasta aquí el panel iba todo en azul, y un dashboard de una sola tinta no
   dice nada: si las cuatro cifras y las cinco barras son del mismo color, el
   color no está trabajando.

   La regla del sistema resuelve cuál va en cada sitio sin abrir una paleta
   nueva: EL COLOR ES EL DEL MÓDULO DEL QUE SALE EL DATO. Las acciones fuera de
   plazo van en naranja porque naranja es Acciones; los registros firmados en
   morado porque morado es Registros. No es decoración, es la misma señal que
   en el menú y en las escenas.
   ========================================================= */
.app__kpiv--incidencias{ color:var(--app-blue) }
.app__kpiv--acciones{ color:var(--app-accion) }
.app__kpiv--registros{ color:var(--app-registro) }
.app__delta--acciones{ color:var(--app-accion) }
.app__delta--registros{ color:var(--app-registro) }

/* Barras y trazos del desglose, cada categoría con su color de módulo. */
.app__fill--incidencias{ background:var(--app-blue) }
.app__fill--acciones{ background:var(--app-accion) }
.app__fill--registros{ background:var(--app-registro) }
.app__fill--kpis{ background:#00A3E0 }

/* --- Las barras del mes, segmentadas ------------------------------------
   Antes la barra era un bloque azul y la única señal de color era el mes en
   curso, más oscuro que los anteriores. Ahora cada barra se parte en los tres
   módulos que la alimentan, con lo que el color pasa a decir DE DÓNDE sale
   cada tramo en vez de sólo cuál es el mes de hoy.

   Los tramos llevan `flex-grow`, no altura: el alto total lo sigue fijando el
   `height` de `.app__barv`, así que la silueta del gráfico es exactamente la
   de antes y el reparto se lee dentro. Y `app-bar-grow` sigue escalando el
   contenedor, así que crecen los tres a la vez, como una sola barra. */
.app__barv{ display:flex; flex-direction:column; overflow:hidden }
.app__bseg{ display:block; min-height:1px }
.app__bseg--incidencias{ background:var(--app-blue) }
.app__bseg--acciones{ background:var(--app-accion) }
.app__bseg--registros{ background:var(--app-registro) }

/* El mes en curso sigue destacando, pero ya no con otro azul —eso obligaría a
   tener una versión clara de cada uno de los tres colores—: los anteriores se
   bajan de intensidad y ya está. */
.app__bar--prev .app__barv{ opacity:.72 }

/* La leyenda va en la misma línea que el título del panel, no debajo: la
   ventana del navegador va recortada por abajo y una fila más de alto se come
   la última banda de la tabla. */
.app__panelt--legend{ display:flex; align-items:center; justify-content:space-between; gap:10px }
.app__legend{ display:flex; align-items:center; gap:9px }
.app__lg{ font-size:9px; color:var(--app-text-2); display:inline-flex; align-items:center; gap:3px }
.app__lg::before{ content:""; width:6px; height:6px; border-radius:2px; background:currentColor }
.app__lg--incidencias::before{ background:var(--app-blue) }
.app__lg--acciones::before{ background:var(--app-accion) }
.app__lg--registros::before{ background:var(--app-registro) }

/* =========================================================
   MOVIMIENTO DENTRO DE LA PANTALLA
   Lo que se mueve es el CONTENIDO de la app, nunca la tarjeta: es la misma
   distinción que hace Stripe —sus tarjetas no animan, sus mockups sí—. Un
   dashboard con las barras quietas parece una captura; con las barras
   creciendo parece la aplicación.

   Y una regla de consumo: TODO esto obedece a --play, que ds/scene.js pone en
   `paused` cuando la escena sale de la pantalla. Una tarjeta que no se ve no
   tiene por qué estar repintando. Si el script no carga, --play se queda en
   `running` y se mueve igual: se degrada hacia el lado bueno.

   Tres límites, para que no se convierta en un salvapantallas:
     · ciclos largos (6-9s) y con reposo;
     · nada se mueve de sitio: crecen, aparecen, parpadean. Si un elemento se
       desplaza, el ojo lo persigue y deja de leer el dato;
     · con prefers-reduced-motion se queda todo quieto y en su estado final.
   ========================================================= */

/* --- El filtro de turno, y todo lo que cuelga de él ----------------------
   El tablero no se mueve por moverse: se mueve porque se está filtrando. Se
   elige un turno y se recalcula TODO —las cuatro cifras, las seis barras, el
   desglose y el listado—, que es lo único que demuestra que el tablero es
   configurable y no una captura.

   RELOJ ÚNICO: 12 s, tres pasos de 4 s, y los cortes en 32 % y 65 %. Son
   exactamente los de `app-cnt1/2/3` y los del filtro de la escena de Acciones,
   así que las dos escenas de la home van sincronizadas. Cualquier cosa que se
   añada a este tablero tiene que entrar en este reloj o se verá desfasada.

   Por qué el TURNO y no el área o la línea: es la única variable que no vacía
   ningún panel. Filtrando por departamento, «Por categoría» se queda con una
   sola barra; por turno, las cuatro categorías siguen existiendo en los dos.

   El gesto de pulsar es el de la escena de Acciones: el chip que se selecciona
   se enciende y abre un anillo. Sin el anillo sólo cambia de color y no se lee
   como una pulsación. */
.app__filters--turno{ margin-bottom:1px }
.app__flabel{
  display:inline-flex; align-items:center; gap:5px;
  font-size:10.5px; color:var(--app-mute); margin-right:2px;
}
.app__flabel svg{ width:12px; height:12px; flex:0 0 auto }

.app__filter--t1,.app__filter--t2,.app__filter--t3{
  animation-duration:12s;
  animation-iteration-count:infinite;
  animation-timing-function:ease-out;
  animation-play-state:var(--play,running);
}
.app__filter--t1{ animation-name:app-tsel-1 }
.app__filter--t2{ animation-name:app-tsel-2 }
.app__filter--t3{ animation-name:app-tsel-3 }
@keyframes app-tsel-1{
  0%,30%   { background:var(--app-blue-wash); border-color:rgba(30,107,255,.28); color:var(--app-blue-deep);
             box-shadow:0 0 0 0 rgba(30,107,255,0) }
  32%,96%  { background:#fff; border-color:var(--app-line); color:var(--app-text-2);
             box-shadow:0 0 0 0 rgba(30,107,255,0) }
  98%      { background:var(--app-blue-wash); border-color:rgba(30,107,255,.28); color:var(--app-blue-deep);
             box-shadow:0 0 0 5px rgba(30,107,255,.22) }
  100%     { background:var(--app-blue-wash); border-color:rgba(30,107,255,.28); color:var(--app-blue-deep);
             box-shadow:0 0 0 0 rgba(30,107,255,0) }
}
@keyframes app-tsel-2{
  0%,30%   { background:#fff; border-color:var(--app-line); color:var(--app-text-2);
             box-shadow:0 0 0 0 rgba(30,107,255,0) }
  33%      { background:var(--app-blue-wash); border-color:rgba(30,107,255,.28); color:var(--app-blue-deep);
             box-shadow:0 0 0 5px rgba(30,107,255,.22) }
  40%,63%  { background:var(--app-blue-wash); border-color:rgba(30,107,255,.28); color:var(--app-blue-deep);
             box-shadow:0 0 0 0 rgba(30,107,255,0) }
  65%,100% { background:#fff; border-color:var(--app-line); color:var(--app-text-2);
             box-shadow:0 0 0 0 rgba(30,107,255,0) }
}
@keyframes app-tsel-3{
  0%,63%   { background:#fff; border-color:var(--app-line); color:var(--app-text-2);
             box-shadow:0 0 0 0 rgba(30,107,255,0) }
  66%      { background:var(--app-blue-wash); border-color:rgba(30,107,255,.28); color:var(--app-blue-deep);
             box-shadow:0 0 0 5px rgba(30,107,255,.22) }
  73%,96%  { background:var(--app-blue-wash); border-color:rgba(30,107,255,.28); color:var(--app-blue-deep);
             box-shadow:0 0 0 0 rgba(30,107,255,0) }
  98%,100% { background:#fff; border-color:var(--app-line); color:var(--app-text-2);
             box-shadow:0 0 0 0 rgba(30,107,255,0) }
}

/* Relevo de tres valores en el mismo hueco. Es `.app__cntswap` sin su
   `margin-left:auto`, y reutiliza sus keyframes a propósito: un solo juego de
   cortes para todo el tablero. La rejilla mide lo que el valor más ancho, así
   que la caja no salta al cambiar de turno. */
.app__sw3{ display:inline-grid; vertical-align:baseline }
.app__sw3 > *{
  grid-area:1/1;
  animation-duration:12s; animation-iteration-count:infinite;
  animation-timing-function:steps(1);
  animation-play-state:var(--play,running);
}
.app__sw3 > :nth-child(1){ animation-name:app-cnt1 }
.app__sw3 > :nth-child(2){ animation-name:app-cnt2 }
.app__sw3 > :nth-child(3){ animation-name:app-cnt3 }

/* La lista de abajo se acorta con el turno: tres vistas en la misma celda para
   que el panel no dé saltos de alto. `.app__views` normal es `flex:1` porque
   ahí ocupa todo el fragmento; dentro de un panel tiene que medir lo suyo. */
.app__views--lista{ flex:0 0 auto; margin-top:6px }
.app__views--lista .app__table{ margin-top:0 }

/* --- Las cifras ruedan de un valor al siguiente --------------------------
   Un panel donde las barras se mueven pero las cifras están fijas se lee como
   una captura con un gif encima. Aquí cada cifra arranca contando desde cero y
   luego RUEDA al valor del turno que se acaba de elegir, justo en el corte.

   El número lo pinta `counter()` a partir de `--app-n`, que es lo que anima el
   keyframe —un entero no se puede interpolar de otra forma en CSS—. Cada
   tarjeta lleva el valor del primer paso en un `style` inline: es lo que se ve
   si el navegador no registra `@property` y no sabe interpolar, y así la cifra
   correcta está en el marcado y no sólo en la hoja de estilos.

   La de días necesita dos contadores porque `counter()` no da decimales: uno
   para el entero y otro para la cifra de después de la coma. */
@property --app-n{ syntax:'<integer>'; initial-value:0; inherits:false }
@property --app-d{ syntax:'<integer>'; initial-value:0; inherits:false }

.app__kpiv--count,.app__kpiv--dec{
  animation-duration:12s;
  animation-timing-function:cubic-bezier(.16,1,.3,1);
  animation-iteration-count:infinite;
  animation-play-state:var(--play,running);
  font-variant-numeric:tabular-nums;   /* si no, la caja baila al pasar de 9 a 10 */
}
.app__kpiv--count{ counter-reset:n var(--app-n) }
.app__kpiv--count::before{ content:counter(n) }
.app__kpiv--dec{ counter-reset:n var(--app-n) d var(--app-d) }
.app__kpiv--dec::before{ content:counter(n) "," counter(d) }

/* Un keyframe por cifra: los valores de un contador tienen que ir literales,
   así que no hay forma de compartirlos. Todos: 4 % arranque, y las ruedas
   centradas en los cortes de 32 % y 65 %.
     abiertas   5 → 3 → 2      fuera de plazo  1 → 1 → 0
     cierre   2,4 → 2,0 → 3,1  registros      96 → 52 → 44 */
.app__kpi:nth-child(1) .app__kpiv{ animation-name:app-n-abiertas }
.app__kpi:nth-child(2) .app__kpiv{ animation-name:app-n-cierre; animation-delay:.08s }
.app__kpi:nth-child(3) .app__kpiv{ animation-name:app-n-plazo;  animation-delay:.16s }
.app__kpi:nth-child(4) .app__kpiv{ animation-name:app-n-firmados; animation-delay:.24s }
@keyframes app-n-abiertas{ 0%{--app-n:0} 4%,28%{--app-n:5}  34%,61%{--app-n:3}  68%,96%{--app-n:2}  100%{--app-n:0} }
@keyframes app-n-plazo   { 0%{--app-n:0} 4%,28%{--app-n:1}  34%,61%{--app-n:1}  68%,96%{--app-n:0}  100%{--app-n:0} }
@keyframes app-n-firmados{ 0%{--app-n:0} 4%,28%{--app-n:96} 34%,61%{--app-n:52} 68%,96%{--app-n:44} 100%{--app-n:0} }
@keyframes app-n-cierre{
  0%      { --app-n:0; --app-d:0 }
  4%,28%  { --app-n:2; --app-d:4 }
  34%,61% { --app-n:2; --app-d:0 }
  68%,96% { --app-n:3; --app-d:1 }
  100%    { --app-n:0; --app-d:0 }
}

/* --- Las barras del mes ---------------------------------------------------
   Crecen desde la base y, en cada corte, se reescalan al peso del turno. Un
   keyframe por mes porque el reparto mañana/tarde no es el mismo en todos, y
   los dos factores de cada mes suman 1: el turno no inventa actividad, la
   reparte. Los `scale` van sobre la altura inline, que es la del turno «Todos».
     ENE .58/.42 · FEB .62/.38 · MAR .60/.40 · ABR .57/.43 · MAY .64/.36 · JUN .60/.40 */
.app__barv{
  transform-origin:50% 100%;
  animation-duration:12s;
  animation-timing-function:cubic-bezier(.16,1,.3,1);
  animation-iteration-count:infinite;
  animation-play-state:var(--play,running);
}
.app__bar:nth-child(1) .app__barv{ animation-name:app-bar-ene; animation-delay:0s }
.app__bar:nth-child(2) .app__barv{ animation-name:app-bar-feb; animation-delay:.09s }
.app__bar:nth-child(3) .app__barv{ animation-name:app-bar-mar; animation-delay:.18s }
.app__bar:nth-child(4) .app__barv{ animation-name:app-bar-abr; animation-delay:.27s }
.app__bar:nth-child(5) .app__barv{ animation-name:app-bar-may; animation-delay:.36s }
.app__bar:nth-child(6) .app__barv{ animation-name:app-bar-jun; animation-delay:.45s }
@keyframes app-bar-ene{ 0%{scale:1 .06} 4%,28%{scale:1 1} 34%,61%{scale:1 .58} 68%,96%{scale:1 .42} 100%{scale:1 .06} }
@keyframes app-bar-feb{ 0%{scale:1 .06} 4%,28%{scale:1 1} 34%,61%{scale:1 .62} 68%,96%{scale:1 .38} 100%{scale:1 .06} }
@keyframes app-bar-mar{ 0%{scale:1 .06} 4%,28%{scale:1 1} 34%,61%{scale:1 .60} 68%,96%{scale:1 .40} 100%{scale:1 .06} }
@keyframes app-bar-abr{ 0%{scale:1 .06} 4%,28%{scale:1 1} 34%,61%{scale:1 .57} 68%,96%{scale:1 .43} 100%{scale:1 .06} }
@keyframes app-bar-may{ 0%{scale:1 .06} 4%,28%{scale:1 1} 34%,61%{scale:1 .64} 68%,96%{scale:1 .36} 100%{scale:1 .06} }
@keyframes app-bar-jun{ 0%{scale:1 .06} 4%,28%{scale:1 1} 34%,61%{scale:1 .60} 68%,96%{scale:1 .40} 100%{scale:1 .06} }

/* Los trazos del desglose se llenan de izquierda a derecha y, en cada corte,
   se reescalan a la proporción del turno. El factor es el cociente de las
   cifras que van escritas al lado, para que la barra y el número digan lo
   mismo: Mantenimiento 5→3→2, Calidad 3→2→1, Producción 2→1→1 y Reclamación
   1→1→0 (por eso ésa desaparece en el turno de tarde). */
.app__fill{
  transform-origin:0 50%;
  animation-duration:12s;
  animation-timing-function:cubic-bezier(.16,1,.3,1);
  animation-iteration-count:infinite;
  animation-play-state:var(--play,running);
}
.app__break:nth-child(1) .app__fill{ animation-name:app-fill-mant }
.app__break:nth-child(2) .app__fill{ animation-name:app-fill-cal;  animation-delay:.1s }
.app__break:nth-child(3) .app__fill{ animation-name:app-fill-prod; animation-delay:.2s }
.app__break:nth-child(4) .app__fill{ animation-name:app-fill-recl; animation-delay:.3s }
@keyframes app-fill-mant{ 0%{scale:0 1} 4%,28%{scale:1 1} 34%,61%{scale:.6 1}  68%,96%{scale:.4 1}   100%{scale:0 1} }
@keyframes app-fill-cal { 0%{scale:0 1} 4%,28%{scale:1 1} 34%,61%{scale:.67 1} 68%,96%{scale:.33 1}  100%{scale:0 1} }
@keyframes app-fill-prod{ 0%{scale:0 1} 4%,28%{scale:1 1} 34%,61%{scale:.5 1}  68%,96%{scale:.5 1}   100%{scale:0 1} }
@keyframes app-fill-recl{ 0%{scale:0 1} 4%,28%{scale:1 1} 34%,61%{scale:1 1}   68%,96%{scale:.02 1}  100%{scale:0 1} }

/* El parte que acaba de entrar en el móvil: aparece y se queda. Es lo que pasa
   de verdad cuando alguien reporta desde la línea. */
.app__mcard--nueva{ animation:app-card-in 8s ease-out infinite; animation-play-state:var(--play,running); animation-iteration-count:var(--iter,infinite); animation-fill-mode:forwards; }
@keyframes app-card-in{
  0%     { opacity:0; translate:0 -6px }
  6%,96% { opacity:1; translate:0 0 }
  100%   { opacity:0; translate:0 -6px }
}
/* Y su punto de aviso late mientras está sin abrir. */
.app__dot{
  display:inline-block; width:5px; height:5px; border-radius:50%;
  background:var(--app-blue); margin-left:5px; vertical-align:middle;
  animation:app-dot 2.4s ease-out infinite;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
}
@keyframes app-dot{
  0%  { box-shadow:0 0 0 0 rgba(30,107,255,.5) }
  70% { box-shadow:0 0 0 6px rgba(30,107,255,0) }
  100%{ box-shadow:0 0 0 0 rgba(30,107,255,0) }
}

/* La ronda se responde en cascada, un control cada medio segundo sobre un
   reloj de 8 s. Lo que se anima es el BOTÓN ELEGIDO pasando de vacío a lleno;
   los dos botones están puestos desde el principio, porque la plantilla ya
   existe y lo que ocurre delante es que alguien la responde.

   El saltito del 6 % es el acuse del toque: sin él, el relleno aparece y no se
   entiende que lo ha pulsado alguien. Va antes de que cambie el color, que es
   el orden en que ocurre de verdad —primero el dedo, después el estado—. */
.app__opt--on{
  animation:app-elige 8s ease-out infinite;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
}
.app__item:nth-child(2) .app__opt--on{ animation-delay:.5s }
.app__item:nth-child(3) .app__opt--on{ animation-delay:1s }
.app__item:nth-child(4) .app__opt--on{ animation-delay:1.5s }
.app__item:nth-child(5) .app__opt--on{ animation-delay:2s }
.app__item:nth-child(6) .app__opt--on{ animation-delay:2.5s }
@keyframes app-elige{
  0%,4%   { background:#fff; color:var(--c); scale:1 }
  6%      { background:#fff; color:var(--c); scale:1.12 }
  9%,96%  { background:var(--c); color:#fff;  scale:1 }
  100%    { background:#fff; color:var(--c); scale:1 }
}
/* `app-ok-in` sigue viva: es la que trae el código de la incidencia que abre el
   KO, medio segundo después de que se pulse. */
@keyframes app-ok-in{
  0%,4%   { opacity:0; scale:.9 }
  10%,96% { opacity:1; scale:1 }
  100%    { opacity:0; scale:.9 }
}

/* — EL REGISTRO RELLENÁNDOSE (8 sep 2026) —
   La cascada de arriba ya existía y sólo marcaba los OK. Faltaban las tres
   piezas que convierten «un registro relleno» en «un registro rellenándose»,
   que es lo que pide el titular de la tarjeta de la home:

   · el código de la incidencia que abre el KO entra DESPUÉS de que se pulse el
     KO, no a la vez: primero se marca mal el control y entonces nace la
     incidencia. Medio segundo basta para que se lea el orden.
   · la firma y el informe llegan al final, cuando la ronda está cerrada.

   Todo cuelga del mismo reloj de 8 s de la cascada. La ronda ocupa los 2,5
   primeros segundos —seis controles cada medio segundo—, así que nada de esto
   puede empezar antes del 31 %. */
.app__items .app__link{
  animation:app-ok-in 8s ease-out infinite;
  animation-delay:.5s;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
}

/* El contador de la cabecera. Es el relevo de tres de la app, pero al reloj de
   la ronda: los valores son texto en el HTML —no `content` de CSS— porque el
   catálogo de traducción sólo ve el marcado. */
.app__sw3--ronda > *{ animation-duration:8s }
.app__sw3--ronda > :nth-child(1){ animation-name:app-ronda1 }
.app__sw3--ronda > :nth-child(2){ animation-name:app-ronda2 }
.app__sw3--ronda > :nth-child(3){ animation-name:app-ronda3 }
@keyframes app-ronda1{ 0%,12%{opacity:1} 13%,100%{opacity:0} }
@keyframes app-ronda2{ 0%,12%{opacity:0} 13%,25%{opacity:1} 26%,100%{opacity:0} }
@keyframes app-ronda3{ 0%,25%{opacity:0} 26%,100%{opacity:1} }

.app__sign--tarde{
  animation:app-firma-in 8s cubic-bezier(.16,1,.3,1) infinite;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
}
@keyframes app-firma-in{
  0%,33%  { opacity:0; translate:0 5px }
  41%,96% { opacity:1; translate:0 0 }
  100%    { opacity:0; translate:0 5px }
}
/* El informe que sale solo. Verde de estado, no azul de enlace: no es algo que
   se pueda pulsar en esta pantalla, es algo que ya ha ocurrido. */
.app__signdoc{
  margin-left:auto;
  display:inline-flex; align-items:center; gap:5px;
  font-size:10px; font-weight:500; color:var(--app-ok);
  white-space:nowrap;
}
.app__signdoc svg{ width:12px; height:12px; flex:0 0 auto }


/* =========================================================
   LOS TRES CUADROS DE /incidencias/
   8 de septiembre de 2026; rehechos el 9 con la aplicación delante.

   La sección «Detecta» enseñaba las mismas cuatro pantallas que la home. Estas
   tres son las que la sustituyen, y ninguna existe en ningún otro sitio: el
   alta de una incidencia paso a paso, el reparto del trabajo a cualquiera del
   equipo, y el análisis de Ishikawa que acaba en un registro programado. Si
   alguna se quiere reutilizar, que sea moviéndola, no copiándola.

   Las tres se comprobaron contra `demo.trysolved.com` el 9 de septiembre de
   2026: el alta contra `/incidents/create-incident-byTags`, el reparto contra
   `/actions/create` y el Ishikawa contra el panel que abre el icono de la
   cabecera de una incidencia. Lo que se ve aquí existe.
   ========================================================= */

/* --- 1 · EL ALTA DE UNA INCIDENCIA, EN TRES PASOS ----------------------------
   Antes esta pantalla era la de AJUSTES: la lista de plantillas de incidencia,
   con los tres tipos relevándose en el filtro y la lista de campos cambiando
   entera. Enseñaba dónde se diseña una plantilla, no para qué sirve.

   Ahora son los tres pasos del alta —Inicio → «Configura los datos iniciales»
   → el asistente guiado—, que es donde la plantilla se nota: la categoría
   enruta a la plantilla (`templatesMapping`) y con ella cambian los campos que
   el asistente pregunta.

   Los tres pasos se APILAN en la misma celda y se relevan, por lo mismo que se
   apilaban las tres listas de campos: miden distinto y en secuencia la caja
   pegaría un salto en cada cambio. El reloj es de 20 s y el reparto no es
   libre: el paso 3 se lleva más de la mitad porque es el único que hay que
   leer, y los dos primeros son el camino hasta él. */
.app__flujo{ flex:1; min-height:0; display:grid; align-items:start }
.app__paso{
  grid-area:1/1;
  display:flex; flex-direction:column; gap:9px;
  animation-duration:20s; animation-iteration-count:infinite;
  animation-timing-function:steps(1);
  animation-play-state:var(--play,running);
  /* El paso que no toca no se lleva el clic ni el foco, aunque la escena entera
     sea decorativa: `opacity:0` deja el nodo en pie. */
  pointer-events:none;
}
.app__paso--a{ animation-name:app-paso-a }
.app__paso--b{ animation-name:app-paso-b }
.app__paso--c{ animation-name:app-paso-c }
@keyframes app-paso-a{ 0%,15%{opacity:1} 16%,100%{opacity:0} }
@keyframes app-paso-b{ 0%,15%{opacity:0} 16%,38%{opacity:1} 39%,100%{opacity:0} }
@keyframes app-paso-c{ 0%,38%{opacity:0} 39%,100%{opacity:1} }

/* PASO 1 · Inicio. La cabecera con el saludo y los tres botones. */
.app__hola{
  display:flex; align-items:center; gap:9px;
  background:#fff; border:1px solid var(--app-line); border-radius:9px;
  padding:10px 12px;
}
.app__ava--hola{ width:28px; height:28px; font-size:12px; background:var(--app-blue) }
.app__holat{ font-size:12px; font-weight:500; color:var(--app-text) }
.app__holas{ font-size:10px; color:var(--app-text-2); margin-top:1px }
/* Los tres botones llevan el color de SU módulo, que es como los pinta la
   aplicación: azul incidencias, naranja acciones, morado registros. Es la misma
   regla del tinte de una escena — el color codifica módulo, no jerarquía. */
.app__crear{
  display:inline-flex; align-items:center; gap:6px;
  padding:6px 11px; border-radius:7px;
  border:1px solid var(--app-line); background:#fff;
  font-size:10.5px; font-weight:500; white-space:nowrap;
}
.app__crear svg{ width:13px; height:13px; flex:0 0 auto }
.app__crear--inc{ color:var(--app-blue) }
.app__crear--acc{ color:var(--app-accion) }
.app__crear--reg{ color:var(--app-registro) }
/* El botón que se pulsa. El saltito del 6 % va ANTES del cambio de color, que
   es el orden en que ocurre de verdad: primero el dedo, después el estado. Es
   la misma decisión que la píldora OK/KO del registro. */
.app__crear--inc{
  animation:app-crear 20s ease-out infinite;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
}
@keyframes app-crear{
  0%,7%    { background:#fff; border-color:var(--app-line); color:var(--app-blue); scale:1 }
  9%       { background:#fff; border-color:var(--app-line); color:var(--app-blue); scale:.94 }
  11%,100% { background:var(--app-blue); border-color:var(--app-blue); color:#fff; scale:1 }
}
.app__abiertas .app__panelt{ margin-bottom:6px }
/* La ruta que trae el paso anterior —categoría y centro— vive en la cabecera
   del asistente. Va en tinta neutra y NO con `.app__recprog`, que es verde:
   ese verde es el de «firmado», y aquí no hay nada firmado todavía. */
.app__ruta{
  margin-left:auto; font-size:10px; color:var(--app-text-2); white-space:nowrap;
}

/* PASO 2 · «Configura los datos iniciales». Dos columnas, como la aplicación:
   el centro y la categoría. La de categoría es más ancha porque lleva seis
   opciones y la otra dos. */
.app__inicial{
  display:grid; grid-template-columns:minmax(0,1fr) minmax(0,1.15fr);
  gap:10px; align-items:start;
}
.app__elecs{ margin-top:8px; display:flex; flex-direction:column; gap:5px }
.app__elec{
  padding:7px 10px;
  border:1px solid var(--app-line); border-radius:7px; background:#fff;
  font-size:10.5px; color:var(--app-text);
}
/* «Seleccionado» se dice con el lavado azul, como el ítem activo del menú
   lateral, no con un relleno. */
.app__elec--centro{
  background:var(--app-blue-wash); border-color:rgba(30,107,255,.28);
  color:var(--app-blue-deep);
}
.app__elec--cat{
  animation:app-elec 20s ease-out infinite;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
}
@keyframes app-elec{
  0%,27%   { background:#fff; border-color:var(--app-line); color:var(--app-text); scale:1 }
  29%      { background:#fff; border-color:var(--app-line); color:var(--app-text); scale:.97 }
  31%,100% { background:var(--app-blue-wash); border-color:rgba(30,107,255,.28); color:var(--app-blue-deep); scale:1 }
}

/* PASO 3 · El asistente guiado: la plantilla ya elegida, hecha preguntas. El
   número y el nombre del campo van a la izquierda, como en la aplicación, y la
   pregunta en lenguaje natural centrada en su propia caja. */
.app__guia{ display:flex; flex-direction:column; gap:8px; min-height:0 }
.app__preg{
  display:grid; grid-template-columns:18px auto minmax(0,1fr);
  align-items:start; gap:9px;
  background:#fff; border:1px solid var(--app-line); border-radius:9px;
  padding:10px 12px;
  animation-duration:20s; animation-iteration-count:infinite;
  animation-timing-function:cubic-bezier(.16,1,.3,1);
  animation-play-state:var(--play,running);
}
.app__preg--q1{ animation-name:app-preg-1 }
.app__preg--q2{ animation-name:app-preg-2 }
.app__preg--q3{ animation-name:app-preg-3 }
@keyframes app-preg-1{ 0%,39%{opacity:0; translate:0 6px} 44%,100%{opacity:1; translate:0 0} }
@keyframes app-preg-2{ 0%,55%{opacity:0; translate:0 6px} 60%,100%{opacity:1; translate:0 0} }
@keyframes app-preg-3{ 0%,71%{opacity:0; translate:0 6px} 76%,100%{opacity:1; translate:0 0} }
.app__pregn{
  width:18px; height:18px; border-radius:5px; margin-top:1px;
  display:grid; place-items:center;
  background:var(--app-blue); color:#fff;
  font-size:9.5px; font-weight:500;
}
.app__pregl{ font-size:10.5px; font-weight:500; color:var(--app-text); padding-top:3px; white-space:nowrap }
.app__pregc{ min-width:0; background:var(--app-canvas); border-radius:7px; padding:8px 10px }
.app__pregq{ font-size:10.5px; color:var(--app-text-2); text-align:center }
.app__pregr{
  margin-top:7px; padding:6px 9px;
  background:#fff; border:1px solid var(--app-line); border-radius:6px;
  font-size:10.5px; color:var(--app-text); min-height:14px;
}
/* La respuesta se escribe DESPUÉS de que aparezca la pregunta: la caja ya está,
   lo que entra es el texto. Sin esto la pantalla enseña un formulario relleno,
   que lo puede fingir cualquiera. */
.app__pregr span{
  display:inline-block;
  animation:app-teclea 20s steps(30) infinite;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
}
@keyframes app-teclea{ 0%,45%{clip-path:inset(0 100% 0 0)} 51%,100%{clip-path:inset(0 0 0 0)} }
/* Los atajos del asistente. No son adorno: son los valores que la aplicación
   ofrece para no teclear, y por eso llevan el color de lo que dicen —el rojo de
   Alta, el ámbar de Media—. Un atajo de prioridad en gris diría que la
   prioridad no tiene estado, que es justo lo contrario. */
.app__atajos{ margin-top:7px; display:flex; flex-wrap:wrap; gap:5px; justify-content:center }
.app__atajo{
  display:inline-flex; padding:4px 10px; border-radius:999px;
  border:1px solid rgba(30,107,255,.4); background:#fff;
  font-size:9.5px; color:var(--app-blue); white-space:nowrap;
}
.app__atajo--alta{ color:var(--app-crit); border-color:rgba(224,49,49,.45) }
.app__atajo--media,.app__atajo--sel{
  animation-duration:20s; animation-iteration-count:infinite;
  animation-timing-function:ease-out;
  animation-play-state:var(--play,running);
}
.app__atajo--media{ animation-name:app-atajo-media }
.app__atajo--sel{ animation-name:app-atajo-sel }
@keyframes app-atajo-media{
  0%,63%   { background:#fff; border-color:rgba(245,159,0,.5); color:var(--app-warn) }
  66%,100% { background:var(--app-warn); border-color:var(--app-warn); color:#fff }
}
@keyframes app-atajo-sel{
  0%,79%   { background:#fff; border-color:rgba(30,107,255,.4); color:var(--app-blue) }
  82%,100% { background:var(--app-blue); border-color:var(--app-blue); color:#fff }
}


/* --- 2 · CREAR LA TAREA Y ASIGNARLA A CUALQUIERA DEL EQUIPO ------------------
   Dos mitades, y la primera se añadió el 9 de septiembre: arriba **la acción
   que se está creando** —cabecera, incidencia enlazada y descripción
   escribiéndose— y debajo **el desplegable de Responsable abierto**. Sin la
   primera esto era un buscador de personas; con ella es asignar una tarea, que
   es lo que dice el titular.

   Va sin marco de aparato —no es una ventana, es un panel— y por eso lleva su
   propia sombra. */
.app__asigna{
  width:100%;
  background:#fff;
  border:1px solid var(--app-line);
  border-radius:11px;
  box-shadow:0 16px 32px rgba(50,50,93,.12);
  padding:12px 13px 13px;
  display:flex; flex-direction:column; gap:9px;
  font-family:Roboto,'Helvetica Neue',Arial,sans-serif;
  color:var(--app-text);
  font-variant-numeric:tabular-nums;
  text-align:left;
}
.app__panelt--nueva{ display:flex; align-items:center; justify-content:space-between; gap:8px }
.app__vinc{ display:inline-flex; align-items:center; gap:5px; color:var(--app-mute) }
.app__vinc svg{ width:12px; height:12px; flex:0 0 auto }
.app__campo{ display:flex; flex-direction:column; gap:4px }
.app__campok{ font-size:9.5px; color:var(--app-mute) }
.app__campov{
  padding:6px 9px; border:1px solid var(--app-line); border-radius:7px;
  font-size:10.5px; color:var(--app-text); min-height:15px;
  overflow:hidden; white-space:nowrap;
}
/* La descripción se teclea delante de quien mira. `steps()` aquí sí, al revés
   que en la banda de IA: allí el texto se pinta a 22px y los saltos de carácter
   se leen como un tartamudeo; a 10,5px son un cursor. */
.app__campov--tecla span{
  display:inline-block;
  animation:app-tecla 12s steps(26) infinite;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
}
@keyframes app-tecla{ 0%,3%{clip-path:inset(0 100% 0 0)} 19%,100%{clip-path:inset(0 0 0 0)} }

/* — LOS CAMPOS QUE SE RELLENAN DELANTE: RESPONSABLE Y FECHA DE CIERRE —
   Cada uno lleva los DOS estados apilados en la misma celda —el marcador de
   posición y el valor—, y lo que se releva es cuál de los dos se ve. No se puede
   cambiar un texto desde CSS, y ponerlo con `content` lo dejaría fuera del
   catálogo de traducción, que lee el marcado. */
.app__campov--pick{
  display:grid; grid-template-columns:minmax(0,1fr) auto;
  align-items:center; gap:6px;
}
.app__campoph,.app__campoval{ grid-column:1; grid-row:1; min-width:0; overflow:hidden }
.app__campoph{ color:var(--app-mute) }
.app__campoval{ display:inline-flex; align-items:center; gap:6px }
.app__campoph,.app__campoval{
  animation-duration:12s; animation-iteration-count:infinite;
  animation-timing-function:ease-out;
  animation-play-state:var(--play,running);
}
.app__campoval{ opacity:0 }
.app__campoico{ width:12px; height:12px; flex:0 0 auto; color:var(--app-mute) }

/* Los cuatro relevos van SECOS —el marcador se apaga antes de que el valor
   entre—, no cruzados: con las dos capas a media opacidad a la vez se leía
   «Pablo Ferrer» encima de «…onsable», que es un pintado roto, no una
   transición. El reloj es de 12 s y el orden es el del formulario: descripción
   (3-19 %), responsable (24-65 %) y fecha de cierre (66-90 %), primero el día y
   después la hora. Si se toca un tramo hay que mirar los otros: el desplegable
   se cierra en el 64 % y el foco de la fecha empieza en el 66; con ellos
   solapados parece que se han tocado dos campos a la vez. */
.app__campo--resp{ position:relative }
.app__campo--resp .app__campov{
  animation:app-foco 12s ease-out infinite;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
}
.app__campov--sel .app__campoph{ animation-name:app-ph }
.app__campov--sel .app__campoval{ animation-name:app-val }
@keyframes app-ph{  0%,60%{opacity:1} 62%,100%{opacity:0} }
@keyframes app-val{ 0%,62%{opacity:0} 65%,94%{opacity:1} 100%{opacity:0} }
/* El anillo de foco: es lo que dice que el campo se ha tocado, y va ANTES de
   que la lista aparezca. Al revés, la lista sale de la nada. */
@keyframes app-foco{
  0%,24%   { border-color:var(--app-line); box-shadow:0 0 0 0 rgba(30,107,255,0) }
  27%,60%  { border-color:rgba(30,107,255,.45); box-shadow:0 0 0 2px var(--app-blue-wash) }
  65%,100% { border-color:var(--app-line); box-shadow:0 0 0 0 rgba(30,107,255,0) }
}

/* — LA FECHA DE CIERRE —
   Dos casillas, día y hora, como en `/actions/create`. El reparto 1,4/1 es el de
   la aplicación: la fecha lleva diez caracteres y la hora cinco, y a mitades la
   fecha se cortaba. Se rellenan una detrás de otra, y el anillo va pasando de una
   a la otra: es lo que dice que se está recorriendo el campo, no que se han
   encendido las dos a la vez. */
.app__campodos{ display:grid; grid-template-columns:1.4fr 1fr; gap:6px }
.app__campov--fecha,.app__campov--hora{
  animation-duration:12s; animation-iteration-count:infinite;
  animation-timing-function:ease-out;
  animation-play-state:var(--play,running);
}
.app__campov--fecha{ animation-name:app-foco-f }
.app__campov--hora{ animation-name:app-foco-h }
.app__campov--fecha .app__campoph{ animation-name:app-ph-f }
.app__campov--fecha .app__campoval{ animation-name:app-val-f }
.app__campov--hora .app__campoph{ animation-name:app-ph-h }
.app__campov--hora .app__campoval{ animation-name:app-val-h }
@keyframes app-foco-f{
  0%,66%   { border-color:var(--app-line); box-shadow:0 0 0 0 rgba(30,107,255,0) }
  69%,76%  { border-color:rgba(30,107,255,.45); box-shadow:0 0 0 2px var(--app-blue-wash) }
  80%,100% { border-color:var(--app-line); box-shadow:0 0 0 0 rgba(30,107,255,0) }
}
@keyframes app-foco-h{
  0%,76%   { border-color:var(--app-line); box-shadow:0 0 0 0 rgba(30,107,255,0) }
  79%,88%  { border-color:rgba(30,107,255,.45); box-shadow:0 0 0 2px var(--app-blue-wash) }
  92%,100% { border-color:var(--app-line); box-shadow:0 0 0 0 rgba(30,107,255,0) }
}
@keyframes app-ph-f{  0%,71%{opacity:1} 73%,100%{opacity:0} }
@keyframes app-val-f{ 0%,73%{opacity:0} 76%,94%{opacity:1} 100%{opacity:0} }
@keyframes app-ph-h{  0%,81%{opacity:1} 83%,100%{opacity:0} }
@keyframes app-val-h{ 0%,83%{opacity:0} 86%,94%{opacity:1} 100%{opacity:0} }
.app__campocar{ width:13px; height:13px; flex:0 0 auto; color:var(--app-mute);
  animation:app-car 12s ease-out infinite;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
}
@keyframes app-car{ 0%,25%{rotate:0deg} 29%,60%{rotate:180deg} 65%,100%{rotate:0deg} }

/* EL DESPLEGABLE FLOTA, no empuja. Es lo que hace un desplegable —tapa lo que
   hay debajo— y además es lo que evita que el panel crezca y encoja en cada
   vuelta del bucle, que hacía saltar la escena entera. */
.app__desple{
  position:absolute; left:0; right:0; top:100%; z-index:3;
  margin-top:5px;
  background:#fff; border:1px solid var(--app-line); border-radius:9px;
  box-shadow:0 14px 30px rgba(50,50,93,.16);
  padding:8px;
  display:flex; flex-direction:column; gap:7px;
  transform-origin:50% 0;
  opacity:0; translate:0 -6px; scale:1 .96;
  animation:app-desple 12s cubic-bezier(.16,1,.3,1) infinite;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
  pointer-events:none;
}
@keyframes app-desple{
  0%,27%   { opacity:0; translate:0 -6px; scale:1 .96 }
  33%,58%  { opacity:1; translate:0 0;    scale:1 1 }
  64%,100% { opacity:0; translate:0 -6px; scale:1 .96 }
}
.app__buscar{
  display:flex; align-items:center; gap:7px;
  padding:6px 9px;
  border:1px solid var(--app-line); border-radius:7px;
  font-size:10.5px; color:var(--app-mute);
}
.app__buscar svg{ width:12px; height:12px; flex:0 0 auto }
.app__pick{ list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:2px }
.app__pickrow{
  display:flex; align-items:center; gap:8px;
  padding:6px 7px; border-radius:7px;
}
.app__picktxt{ display:flex; flex-direction:column; min-width:0 }
.app__picktxt b{ font-size:11px; font-weight:500; color:var(--app-text) }
.app__picktxt i{ font-size:9.5px; font-style:normal; color:var(--app-mute) }
/* LAS CARAS. La aplicación pinta la inicial; la foto es licencia del sitio,
   pedida por el cliente y ya tomada en el vídeo de la banda de IA. El tamaño se
   ancla con dos clases porque `ds/scene.css` estira al 100 % cualquier <img>
   que haya dentro de una escena (0-1-1) y aquí hay que ganarle. */
.app__asigna .app__ava--foto{
  width:20px; height:20px; object-fit:cover;
  background:var(--app-canvas);
  box-shadow:inset 0 0 0 1px rgba(11,18,32,.08);
}
/* El foco baja por la lista y se para en quien toca. Se anima el FONDO de la
   fila y no un borde: el borde metería un píxel de alto y la lista bailaría.
   Empieza en el 34 %, que es cuando el desplegable ya está abierto del todo. */
.app__pickrow{
  animation:app-pick 12s ease-out infinite;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
}
.app__pickrow:nth-child(2){ animation-delay:.42s }
.app__pickrow:nth-child(3){ animation-delay:.84s }
.app__pickrow:nth-child(4){ animation-delay:1.26s }
@keyframes app-pick{
  0%,34%  { background:transparent }
  36%,39% { background:var(--app-canvas) }
  42%,100%{ background:transparent }
}
/* Y el elegido se queda encendido hasta que el desplegable se cierra. */
.app__pickrow--on{
  animation:app-pick-on 12s ease-out infinite;
  animation-delay:0s;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
}
@keyframes app-pick-on{
  0%,45%   { background:transparent }
  48%,58%  { background:var(--app-blue-wash) }
  62%,100% { background:transparent }
}
/* El tic de «éste». Va con el mismo reloj que el fondo de su fila: es el mismo
   gesto, no dos. */
.app__tic{
  margin-left:auto; display:inline-grid; place-items:center;
  color:var(--app-blue);
  opacity:0;
  animation:app-tic 12s ease-out infinite;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
}
.app__tic svg{ width:13px; height:13px; display:block }
@keyframes app-tic{ 0%,45%{opacity:0; scale:.7} 49%,58%{opacity:1; scale:1} 62%,100%{opacity:0; scale:.7} }

/* --- 3 · EL ANÁLISIS, LA ESPINA Y EL AVISO -----------------------------------
   Tres tiempos en una sola caja y una sola historia: el panel de Ishikawa de la
   aplicación rellenándose, ese mismo análisis dibujado como espina de pescado, y
   el aviso del registro programado entrando solapado encima.

   ⚠️ LA ESPINA NO EXISTE EN LA APLICACIÓN. Comprobado dos veces en
   `demo.trysolved.com` el 9 de septiembre de 2026: el panel de Ishikawa es una
   lista de las seis emes y no hay vista de diagrama ni botón que la abra. Se
   dibuja a petición expresa del cliente, y por eso va **encadenada al panel de
   verdad**: primero se ve la pantalla que existe y después su lectura. Es la
   segunda licencia consciente de esta página, junto con las caras del equipo.

   Los dos primeros tiempos se apilan en la misma celda y se funden. Se funden y
   no se relevan de golpe —al contrario que los tres pasos del alta, que van con
   `steps(1)`— porque aquí lo que se cuenta es que una cosa **se convierte** en
   la otra; un corte diría que son dos pantallas distintas. */
.app__raiz{
  position:relative;
  width:100%; height:100%;
  display:grid;
  grid-template-columns:minmax(0,1fr);
  grid-template-rows:minmax(0,1fr);
  font-family:Roboto,'Helvetica Neue',Arial,sans-serif;
  color:var(--app-text);
  font-variant-numeric:tabular-nums;
  text-align:left;
}
.app__raizs{ margin:3px 0 0; font-size:10px; color:var(--app-mute) }
.app__fase{
  grid-area:1/1; min-width:0;
  animation-duration:16s; animation-iteration-count:infinite;
  animation-timing-function:cubic-bezier(.16,1,.3,1);
  animation-play-state:var(--play,running);
}
/* El panel se centra en la caja; la espina la llena, porque su geometría es la
   caja. Sin esto el panel se estiraba a lo alto y las seis tarjetas quedaban
   flotando en un recuadro vacío. */
.app__fase--panel{ align-self:center; animation-name:app-fase-panel }
.app__fase--espina{ align-self:start; animation-name:app-fase-espina }
@keyframes app-fase-panel{  0%,38%{opacity:1; scale:1}     47%,100%{opacity:0; scale:.985} }
@keyframes app-fase-espina{ 0%,39%{opacity:0; scale:.985}  48%,100%{opacity:1; scale:1} }

/* — TIEMPO 1 · el panel de la aplicación — */
.app__panel--ish{
  background:#fff;
  box-shadow:0 10px 24px rgba(50,50,93,.08);
}
.app__ish{
  margin-top:9px;
  display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:8px;
}
.app__ishm{
  background:var(--app-canvas); border:1px solid var(--app-line); border-radius:8px;
  padding:8px 9px 9px;
}
.app__ishmt{ font-size:10px; font-weight:500; color:var(--app-text) }
/* La causa arranca ESCONDIDA EN EL PROPIO ELEMENTO, no sólo en el fotograma 0 %.
   `animation-delay` no pinta el 0 % mientras espera —lo aprendimos con la bolita
   del esquema de la home—, así que sin `opacity:0` aquí las seis causas estarían
   puestas desde el primer instante y el escalonado no se vería. El precio es que
   en movimiento reducido hay que encenderlas a mano. */
.app__ishc{
  margin-top:6px; padding:6px 8px;
  background:#fff; border:1px solid var(--app-line); border-radius:6px;
  font-size:9.5px; color:var(--app-text-2); line-height:1.35;
  opacity:0;
  animation:app-ish 16s cubic-bezier(.16,1,.3,1) infinite;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
}
.app__ishm--m2 .app__ishc{ animation-delay:.3s }
.app__ishm--m3 .app__ishc{ animation-delay:.6s }
.app__ishm--m4 .app__ishc{ animation-delay:.9s }
.app__ishm--m5 .app__ishc{ animation-delay:1.2s }
.app__ishm--m6 .app__ishc{ animation-delay:1.5s }
@keyframes app-ish{ 0%,2%{opacity:0; translate:0 4px} 8%,100%{opacity:1; translate:0 0} }
/* La causa raíz, la que acaba justificando el registro. Lavado azul, que es como
   esta aplicación dice «ésta». */
.app__ishc--raiz{
  background:var(--app-blue-wash); border-color:rgba(30,107,255,.3);
  color:var(--app-blue-deep);
}
/* El campo vacío de la aplicación. Va punteado porque lo que dice es «aquí cabe
   otra», y un borde continuo lo leería como un dato más. */
.app__ishw{
  margin-top:5px; padding:6px 8px;
  border:1px dashed var(--app-line-2); border-radius:6px;
  font-size:9.5px; color:var(--app-mute);
}

/* — TIEMPO 2 · la espina —
   El esqueleto va en SVG y los rótulos en HTML encima, que es como está hecho el
   esquema de la home: un <text> dentro de un SVG no se envuelve en varias líneas
   ni escala con la caja, y aquí las causas son frases.

   El SVG va con `preserveAspectRatio="none"` para que la espina llene la caja sea
   cual sea su proporción; el grosor se salva con `vector-effect`, sin el cual el
   estirado deja las raspas más gordas por un lado que por otro.

   LA GEOMETRÍA NO ES LIBRE. La columna mide 72 de 100 y la cabeza se lleva el
   resto: con la columna hasta el 86 —que fue lo primero— la cabeza se quedaba en
   107px y la descripción de la incidencia no cabía en tres líneas. Y las raspas
   arrancan en y=18 y y=42, no en el canto, porque los rótulos ocupan el 30 %
   de arriba y el 30 % de abajo. */
/* EL 88 % NO ES DECORATIVO. El encuadre `b` mete la caja 26 % por arriba y la
   saca un 10 % por abajo, así que la última banda de la escena cae FUERA de la
   tarjeta y la recorta su overflow. Con la espina al 100 % lo que se perdía eran
   los tres rótulos de abajo, que es media espina. Al 88 % el dibujo entero cae
   dentro, y lo que sangra por abajo es lienzo. Si algún día esta escena cambia de
   encuadre, hay que rehacer esta cuenta y la del aviso. */
.app__espina{ position:relative; width:100%; height:88% }
.app__espinasvg{ position:absolute; inset:0; width:100%; height:100% }
.app__espinal,.app__espinar{
  fill:none; stroke:var(--app-line-2); stroke-linecap:round;
  vector-effect:non-scaling-stroke;
}
.app__espinal{ stroke:var(--app-blue); stroke-width:1.6 }
.app__espinar{ stroke:var(--app-line-2); stroke-width:1.3 }
.app__raspa{
  position:absolute; width:22%;
  display:flex; flex-direction:column; gap:3px;
}
/* Los rótulos se anclan al ARRANQUE de su raspa, no al canto de la caja: las
   raspas salen de y=14 y y=46 sobre 60, o sea del 23,3 % y del 76,7 %. Anclados
   al canto quedaban a 120px de su propia raspa y el dibujo dejaba de leerse como
   un dibujo. */
.app__raspa--r1,.app__raspa--r2,.app__raspa--r3{ bottom:77% }
.app__raspa--r4,.app__raspa--r5,.app__raspa--r6{ top:77% }
.app__raspa--r1,.app__raspa--r4{ left:0 }
.app__raspa--r2,.app__raspa--r5{ left:22% }
.app__raspa--r3,.app__raspa--r6{ left:44% }
.app__raspat{
  align-self:flex-start;
  padding:3px 8px; border-radius:999px;
  background:var(--app-canvas); border:1px solid var(--app-line);
  font-size:9.5px; font-weight:500; color:var(--app-text);
}
.app__raspac{ font-size:9.5px; color:var(--app-text-2); line-height:1.35 }
/* La raíz, la misma que iba en lavado azul en el panel: es el mismo dato y tiene
   que reconocerse al pasar de una forma a la otra. */
.app__raspa--raiz .app__raspat{
  background:var(--app-blue-wash); border-color:rgba(30,107,255,.3);
  color:var(--app-blue-deep);
}
.app__raspa--raiz .app__raspac{ color:var(--app-blue-deep) }
/* La cabeza: la incidencia, que es el efecto. Va en tarjeta y no en texto suelto
   porque es lo único de la espina que ya existe como ficha en la aplicación. */
.app__cabeza{
  position:absolute; right:0; top:50%; translate:0 -50%;
  width:29%;
  display:flex; flex-direction:column; gap:3px;
  background:#fff; border:1px solid var(--app-line); border-radius:9px;
  box-shadow:0 10px 24px rgba(50,50,93,.08);
  padding:9px 10px;
}
.app__cabezat{ font-size:10px; color:var(--app-text); line-height:1.35 }
/* Las seis raspas y la cabeza entran con la espina, escalonadas: el dibujo se
   monta, no aparece hecho. Van escondidas en el propio elemento por lo mismo que
   las causas del panel. */
.app__raspa,.app__cabeza{
  opacity:0;
  animation-duration:16s; animation-iteration-count:infinite;
  animation-timing-function:cubic-bezier(.16,1,.3,1);
  animation-play-state:var(--play,running);
  animation-name:app-raspa;
}
.app__raspa--r2{ animation-delay:.22s }
.app__raspa--r3{ animation-delay:.44s }
.app__raspa--r4{ animation-delay:.11s }
.app__raspa--r5{ animation-delay:.33s }
.app__raspa--r6{ animation-delay:.55s }
.app__cabeza{ animation-delay:.66s }
@keyframes app-raspa{
  0%,44%   { opacity:0; translate:0 4px }
  52%,100% { opacity:1; translate:0 0 }
}
/* La cabeza no puede llevar el `translate` de las raspas: su sitio lo fija un
   `translate:0 -50%` que la centra sobre la columna, y el fotograma lo pisaría.
   Se le anima sólo la opacidad. */
.app__cabeza{ animation-name:app-cabeza }
@keyframes app-cabeza{ 0%,46%{opacity:0} 54%,100%{opacity:1} }

/* — TIEMPO 3 · el aviso, encima de la espina —
   Va SOLAPADO a propósito: es una notificación, y una notificación tapa lo que
   hay debajo. Cae sobre la esquina de abajo a la derecha, que es donde menos se
   lleva por delante —una raspa y el canto de la cabeza—.

   El icono es el de Registros y va en su morado: el color codifica módulo, y lo
   que se programa es un registro, no una incidencia. */
.app__aviso{
  position:absolute; right:0; bottom:15%; z-index:4;
  width:min(62%,300px);
  display:flex; align-items:flex-start; gap:9px;
  background:#fff; border:1px solid var(--app-line); border-radius:10px;
  box-shadow:0 18px 40px rgba(50,50,93,.18);
  padding:10px 12px;
  opacity:0; translate:0 10px;
  animation:app-aviso 16s cubic-bezier(.16,1,.3,1) infinite;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
}
@keyframes app-aviso{
  0%,64%  { opacity:0; translate:0 10px }
  70%,96% { opacity:1; translate:0 0 }
  100%    { opacity:0; translate:0 10px }
}
.app__avisoico{
  width:26px; height:26px; border-radius:7px; flex:0 0 auto;
  display:grid; place-items:center;
  background:rgba(124,58,237,.1); color:var(--app-registro);
}
.app__avisoico svg{ width:14px; height:14px; display:block }
.app__avisotxt{ min-width:0 }
.app__avisot{ font-size:10px; color:var(--app-mute) }
.app__avisoq{ font-size:11px; font-weight:500; color:var(--app-text); margin-top:2px }
.app__avisom{ font-size:10px; color:var(--app-text-2); margin-top:3px }

@media (max-width:899px){
  /* Las seis emes pasan a dos columnas: a tres, el rótulo de «Mano de obra»
     parte en dos líneas y la tarjeta deja de leerse de un golpe. */
  .app__ish{ grid-template-columns:repeat(2,minmax(0,1fr)) }
  /* Los dos paneles del alta se apilan: a dos columnas, la lista de categorías
     se queda en un dedo de ancho. */
  .app__inicial{ grid-template-columns:minmax(0,1fr) }
  /* La espina se queda, pero el aviso pasa a ocupar el ancho: a 300px sobre una
     caja estrecha tapaba media espina en vez de una esquina. */
  .app__aviso{ width:100%; right:auto; left:0 }
  .app__raspac{ font-size:9px }
}

@media (prefers-reduced-motion:reduce){
  .app__barv,.app__fill,.app__mcard--nueva,.app__dot,.app__opt--on,
  .app__chat p,.app__cursor,
  .app__items .app__link,.app__sign--tarde,
  .app__ask--escribe > span,.app__answer,.app__iabar i{
    /* `clip-path` lleva `!important` a propósito (30 sep 2026): esta regla es
       de antes que `.app__chat--guion` (8 sep 2026), y esa sección redeclara
       `.app__ask--escribe > span` con su propio `clip-path:inset(...)` más
       abajo en el fichero, con la MISMA especificidad (0-1-1). Sin
       `!important` gana la que va después en el código, no la que resuelve
       el movimiento reducido: la pregunta se quedaba con el `clip-path` al
       100 %, invisible del todo, en cualquier ancho, para quien pide menos
       movimiento. Comprobado con `getComputedStyle` en `/ia/`, paneles
       «Generación de informes» y «Pregunta a tus datos». */
    animation:none !important; opacity:1; scale:1; translate:0 0; width:auto; clip-path:none !important;
  }
  /* Los tres cuadros de /incidencias/ se quedan en su estado resuelto, que es el
     que ya cuenta la historia entera. Van en reglas aparte y NO con el
     `width:auto` de arriba, que aquí no pinta nada y en su día igualaba las
     barras del reparto de causas. */
  .app__paso,.app__crear--inc,.app__elec--cat,
  .app__preg,.app__pregr span,.app__atajo--media,.app__atajo--sel,
  .app__campov--tecla span,
  .app__campo--resp .app__campov,.app__campov--fecha,.app__campov--hora,
  .app__campoph,.app__campocar,
  .app__desple,.app__pickrow,.app__tic,
  .app__ishc,.app__fase,.app__raspa,.app__aviso{
    animation:none !important; opacity:1; scale:1; translate:0 0;
    clip-path:none !important;
  }
  /* De los dos tiempos de la causa raíz se queda LA ESPINA, con su aviso encima:
     es el fotograma en el que el análisis ya está hecho y el registro ya está
     programado, que es la promesa entera del titular. El panel de la aplicación
     se queda debajo, tapado por ella, y por eso se apaga a mano. */
  .app__fase--panel{ opacity:0 }
  /* La cabeza mantiene su centrado, que vive en un `translate` y no en un
     fotograma: la regla de arriba se lo llevaría por delante. */
  .app__cabeza{ animation:none !important; opacity:1; translate:0 -50% }
  /* El desplegable se queda ABIERTO, que es el fotograma que más dice: lo que
     esta escena promete es que ahí sale el equipo entero. Y si se queda abierto,
     los campos se quedan en su marcador de posición: el de responsable con el
     anillo de foco puesto —es el estado que le toca a un desplegable desplegado—
     y los dos de la fecha sin él, que además caen tapados por la lista. */
  .app__campoval{ animation:none !important; opacity:0 }
  .app__campo--resp .app__campov{
    border-color:rgba(30,107,255,.45); box-shadow:0 0 0 2px var(--app-blue-wash);
  }
  .app__campocar{ rotate:180deg }
  /* Del alta se ve el ÚLTIMO paso —el asistente con la plantilla puesta—, que es
     el que la tarjeta promete; los dos primeros son el camino. Si se dejara el
     primero, la escena enseñaría un botón sin pulsar. */
  .app__paso--a,.app__paso--b{ opacity:0 }
  /* Y los estados que ponía el keyframe se encienden a mano: el botón pulsado,
     la categoría elegida y los dos atajos. */
  .app__crear--inc{ background:var(--app-blue); border-color:var(--app-blue); color:#fff }
  .app__elec--cat{
    background:var(--app-blue-wash); border-color:rgba(30,107,255,.28); color:var(--app-blue-deep);
  }
  .app__atajo--media{ background:var(--app-warn); border-color:var(--app-warn); color:#fff }
  .app__atajo--sel{ background:var(--app-blue); border-color:var(--app-blue); color:#fff }
  /* El barrido del selector no ocurre: se queda encendido el elegido. */
  .app__pickrow{ background:transparent }
  .app__pickrow--on{ background:var(--app-blue-wash) }
  /* Sin movimiento no hay nada que pensar ni cursor que parpadee: la pantalla
     se queda en la conversación ya ocurrida, que es la que cuenta lo mismo. */
  .app__think,.app__ask--escribe::after{ display:none }
  /* Las cifras se quedan quietas en su valor final: el `style` inline de cada
     tarjeta ya lleva ese número, así que basta con no animarlas. */
  .app__kpiv--count,.app__kpiv--dec{ animation:none !important }
  /* El filtro se queda en el primer turno y el tablero, en sus cifras: se
     enciende «Todos» a mano —su estado seleccionado lo ponía el keyframe— y de
     los relevos de tres sólo queda el primer valor. */
  .app__filter--t1,.app__filter--t2,.app__filter--t3,
  .app__sw3 > *,.app__views--lista .app__view{ animation:none !important; opacity:1 }
  .app__filter--t1{
    background:var(--app-blue-wash); border-color:rgba(30,107,255,.28); color:var(--app-blue-deep);
  }
  .app__sw3 > :nth-child(2),.app__sw3 > :nth-child(3),
  .app__views--lista .app__view--b,.app__views--lista .app__view--c{ opacity:0 }
}

/* Mismo estado resuelto, para cuando la capacidad del rotador ya terminó su
   vuelta única (ver la nota junto a .scene[data-terminado] en ds/scene.css). */
.scene[data-terminado] .app__barv,
.scene[data-terminado] .app__fill,
.scene[data-terminado] .app__mcard--nueva,
.scene[data-terminado] .app__dot,
.scene[data-terminado] .app__opt--on,
.scene[data-terminado] .app__chat p,
.scene[data-terminado] .app__cursor,
.scene[data-terminado] .app__items .app__link,
.scene[data-terminado] .app__sign--tarde,
.scene[data-terminado] .app__ask--escribe > span,
.scene[data-terminado] .app__answer,
.scene[data-terminado] .app__iabar i,
.scene[data-terminado] .app__iastackseg,
.scene[data-terminado] .app__ialeg{ animation:none !important; opacity:1; scale:1; translate:0 0; width:auto; clip-path:none !important; }
.scene[data-terminado] .app__paso,
.scene[data-terminado] .app__crear--inc,
.scene[data-terminado] .app__elec--cat,
.scene[data-terminado] .app__preg,
.scene[data-terminado] .app__pregr span,
.scene[data-terminado] .app__atajo--media,
.scene[data-terminado] .app__atajo--sel,
.scene[data-terminado] .app__campov--tecla span,
.scene[data-terminado] .app__campo--resp .app__campov,
.scene[data-terminado] .app__campov--fecha,
.scene[data-terminado] .app__campov--hora,
.scene[data-terminado] .app__campoph,
.scene[data-terminado] .app__campocar,
.scene[data-terminado] .app__desple,
.scene[data-terminado] .app__pickrow,
.scene[data-terminado] .app__tic,
.scene[data-terminado] .app__ishc,
.scene[data-terminado] .app__fase,
.scene[data-terminado] .app__raspa,
.scene[data-terminado] .app__aviso{ animation:none !important; opacity:1; scale:1; translate:0 0;
    clip-path:none !important; }
.scene[data-terminado] .app__fase--panel{ opacity:0 }
.scene[data-terminado] .app__cabeza{ animation:none !important; opacity:1; translate:0 -50% }
.scene[data-terminado] .app__campoval{ animation:none !important; opacity:0 }
.scene[data-terminado] .app__campo--resp .app__campov{ border-color:rgba(30,107,255,.45); box-shadow:0 0 0 2px var(--app-blue-wash); }
.scene[data-terminado] .app__campocar{ rotate:180deg }
.scene[data-terminado] .app__paso--a,
.scene[data-terminado] .app__paso--b{ opacity:0 }
.scene[data-terminado] .app__crear--inc{ background:var(--app-blue); border-color:var(--app-blue); color:#fff }
.scene[data-terminado] .app__elec--cat{ background:var(--app-blue-wash); border-color:rgba(30,107,255,.28); color:var(--app-blue-deep); }
.scene[data-terminado] .app__atajo--media{ background:var(--app-warn); border-color:var(--app-warn); color:#fff }
.scene[data-terminado] .app__atajo--sel{ background:var(--app-blue); border-color:var(--app-blue); color:#fff }
.scene[data-terminado] .app__pickrow{ background:transparent }
.scene[data-terminado] .app__pickrow--on{ background:var(--app-blue-wash) }
.scene[data-terminado] .app__think,
.scene[data-terminado] .app__ask--escribe::after{ display:none }
.scene[data-terminado] .app__kpiv--count,
.scene[data-terminado] .app__kpiv--dec{ animation:none !important }
.scene[data-terminado] .app__filter--t1,
.scene[data-terminado] .app__filter--t2,
.scene[data-terminado] .app__filter--t3,
.scene[data-terminado] .app__sw3 > *,
.scene[data-terminado] .app__views--lista .app__view{ animation:none !important; opacity:1 }
.scene[data-terminado] .app__filter--t1{ background:var(--app-blue-wash); border-color:rgba(30,107,255,.28); color:var(--app-blue-deep); }
.scene[data-terminado] .app__sw3 > :nth-child(2),
.scene[data-terminado] .app__sw3 > :nth-child(3),
.scene[data-terminado] .app__views--lista .app__view--b,
.scene[data-terminado] .app__views--lista .app__view--c{ opacity:0 }

/* =========================================================
   PANTALLA DE IA — "Pregunta a tus datos"
   Una de las seis funciones de IA de la plataforma. Va sobre grafito, como
   todo lo de IA, y por eso invierte los colores de la app.
   ========================================================= */
.app--ia{
  background:var(--color-graphite-900,#111214);
  color:#E7E9EC;
}
.app--ia .app__top{ background:#1B2026; border-bottom:1px solid #2A2E34 }
.app__chat{
  flex:1; min-height:0;
  padding:14px 16px;
  display:flex; flex-direction:column; gap:12px;
  min-width:var(--app-chat-w,0);
}
.app__ask{
  align-self:flex-end;
  max-width:74%;
  background:#23282F;
  border:1px solid #33383F;
  border-radius:10px 10px 3px 10px;
  padding:8px 11px;
  font-size:11.5px; line-height:1.35; color:#E7E9EC;
}
.app__answer{
  display:flex; gap:9px; align-items:flex-start;
  max-width:88%;
}
.app__aiico{
  flex:0 0 auto;
  width:22px; height:22px; border-radius:6px;
  display:grid; place-items:center;
  background:rgba(168,85,247,.16);
  color:#C88BFA;
}
.app__aiico svg{ width:13px; height:13px; display:block }
.app__answer > div{ min-width:0 }
.app__chat p{
  margin:0 0 6px;
  font-size:11.5px; line-height:1.45; color:#B9BEC6;
  animation:app-line-in 9s ease-out infinite;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
}
.app__chat p:nth-of-type(2){ animation-delay:.5s }
.app__chat p:nth-of-type(3){ animation-delay:1s }
@keyframes app-line-in{
  0%,6%   { opacity:0 }
  12%,96% { opacity:1 }
  100%    { opacity:0 }
}

/* =========================================================
   LA CONVERSACIÓN ENTERA (.app__chat--guion) — 8 sep 2026
   La pantalla de arriba enseña la respuesta ya escrita, y eso lo puede fingir
   cualquiera. Esta variante enseña el gesto completo, que es lo que separa «un
   chat» de «un chat sobre tus datos»: se escribe la pregunta, la IA piensa, y
   contesta con palabras y con un gráfico.

   UN SOLO RELOJ DE 12 s PARA LOS CUATRO TIEMPOS. Está escrito en el marcado de
   la escena y se repite aquí porque quien toque un número tiene que tocar los
   cuatro:
     0–18 %   se escribe la pregunta
     18–30 %  los tres puntos de pensar
     30–40 %  las dos líneas de la respuesta
     42–54 %  suben las barras
   Si la respuesta empieza antes del 18 %, la IA contesta a media pregunta.
   ========================================================= */

/* — Se escribe la pregunta —
   La burbuja crece con el texto porque lo que se revela es el <span> de
   dentro, no la burbuja: un `clip-path` de derecha a izquierda sobre el
   texto en `nowrap`. No hay `steps()` a propósito: a este cuerpo de letra los
   saltos de carácter se ven como un tartamudeo, y lo que vende el gesto es el
   cursor de la punta, no el escalón.

   30 sep 2026: ERA `width` de 0 a 100 %, y el 100 % no se leía nunca entero
   —comprobado con `javascript_tool` en esta página y en `/ia/`, que reusa el
   mismo componente—: el span calculaba su «100 %» contra el ancho de
   `.app__ask`, y `.app__ask` a su vez mide su propio ancho por el contenido
   DE ESE MISMO SPAN (`align-self:flex-end`, sin `width` propio). Es la
   dependencia circular que la propia hoja evitaba en otras piezas —«si el
   containing block depende del ancho del elemento, el resultado es
   indefinido»— y aquí el navegador se quedaba con un valor congelado más
   estrecho que el texto real: la pregunta se cortaba por el final sin que
   `overflow` hiciera saltar ningún aviso. Con `clip-path` el span nunca
   anima su propio tamaño —mide su ancho natural desde el primer fotograma,
   sin ambigüedad para `.app__ask`— y lo único que cambia con el reloj es
   cuánto de ese ancho se ve. */
.app__ask--escribe{ position:relative }
.app__ask--escribe > span{
  display:block;
  white-space:nowrap;
  clip-path:inset(0 100% 0 0);
  animation:app-ia-teclea 12s linear infinite;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
}
@keyframes app-ia-teclea{
  0%       { clip-path:inset(0 100% 0 0) }
  18%,96%  { clip-path:inset(0 0 0 0) }
  100%     { clip-path:inset(0 100% 0 0) }
}
/* El cursor de escribir vive en la burbuja y sólo mientras se escribe: pasado
   el 18 % la pregunta está mandada y ahí ya no parpadea nada. */
.app__ask--escribe::after{
  content:"";
  position:absolute; top:8px; bottom:8px; right:7px; width:1.5px;
  background:#C88BFA;
  animation:app-ia-caret 12s steps(1) infinite;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
}
@keyframes app-ia-caret{
  0%,3%    { opacity:1 }
  4%,7%    { opacity:0 }
  8%,11%   { opacity:1 }
  12%,15%  { opacity:0 }
  16%,18%  { opacity:1 }
  19%,100% { opacity:0 }
}

/* En teléfono la pregunta no cabe en una línea (15 sep 2026): la conversación
   de la home mide 207px de ancho en un iPhone y «¿Cuántas veces ha fallado la
   cinta L3 este semestre?» necesita 290 — con `nowrap` se cortaba en «la
   cinta l» y la respuesta contestaba a media pregunta. El teclado línea a
   línea no se puede hacer con texto que salta de línea (la anchura animada
   recoloca los saltos mientras crece), así que en la escena, y sólo por debajo
   de 600px, la pregunta se deja envolver y ENTRA en el mismo tramo del reloj
   (0–18 %) con un fundido en vez de tecleándose. Se pierde el gesto de
   escribir y se gana leer la pregunta entera, que es lo que da sentido a la
   respuesta. El cursor se va con el teclado: sobre dos líneas era una barra
   de arriba abajo, no un cursor. */
@media (max-width:600px){
  .scene .app__ask--escribe{ max-width:92% }
  .scene .app__ask--escribe > span{ white-space:normal; width:auto; clip-path:none; animation-name:app-ia-pregunta-entra }
  .scene .app__ask--escribe::after{ display:none }
}
@keyframes app-ia-pregunta-entra{
  0%,4%    { opacity:0 }
  16%,96%  { opacity:1 }
  100%     { opacity:0 }
}

/* LA BURBUJA SE CORTABA POR EL SANGRADO DEL RECORTE, NO POR SU PROPIO ANCHO
   (30 sep 2026, en /ia/). `.app__ask{align-self:flex-end}` pega la burbuja al
   canto derecho de `.app__chat`, y eso es justo lo que un `data-crop="dev-br"`
   no puede prometer: ese recorte SANGRA por la derecha a propósito
   (`left:13%; width:104%`, ver `ds/scene.css`) para que el aparato se lea como
   un fragmento que continúa. En un `data-crop="b"` (la home) no pasa nada —esa
   ventana no sangra por ningún lado—, pero en los paneles 3 y 4 del rotador de
   `/ia/` («Generación de informes con IA» y «Pregunta a tus datos») la misma
   pieza vive dentro de `dev-br`, y la burbuja —ya envuelta, ya con su ancho
   bien medido— quedaba con el final de la frase en la franja que el propio
   sistema recorta. Comprobado con `getBoundingClientRect()` de la burbuja
   contra el borde real de la escena, no sólo con la captura: de 34px a más de
   100px según el ancho, en las nueve anchuras de prueba, de 320 a 1440.

   No se toca el ancho de la burbuja (`.app__ask{max-width:74%}` sigue
   sirviendo a las escenas con crop `b`): se le añade a `.app__chat`, sólo
   dentro de un `dev-br`, el margen derecho que ese recorte ya sangra —los
   mismos 17 puntos que separan `width:104%` de `left:13%` de un rectángulo
   sin sangrado—, así que lo que se ve encajado es la burbuja entera, no el
   35–40 % que cabía antes del recorte. */
.scene__media[data-crop="dev-br"] .app__chat{ padding-right:calc(16px + 17%) }

/* — La IA piensa —
   Tres puntos en la misma caja que va a ocupar la respuesta, para que al
   relevarse no salte nada de sitio. */
.app__think{
  display:flex; align-items:center; gap:5px;
  padding-left:31px;                 /* el ancho del icono de IA + su hueco */
  animation:app-ia-piensa 12s steps(1) infinite;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
}
.app__think span{
  width:5px; height:5px; border-radius:50%;
  background:#C88BFA;
  animation:app-ia-punto 1.1s ease-in-out infinite;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
}
.app__think span:nth-child(2){ animation-delay:.15s }
.app__think span:nth-child(3){ animation-delay:.3s }
@keyframes app-ia-piensa{ 0%,17%{opacity:0} 18%,29%{opacity:1} 30%,100%{opacity:0} }
@keyframes app-ia-punto{ 0%,100%{opacity:.3; translate:0 0} 50%{opacity:1; translate:0 -3px} }

/* — La respuesta —
   Entra cuando la IA deja de pensar, no antes. Reemplaza el reloj de 9 s de la
   pantalla base; sin esto las líneas se verían durante la pregunta. */
.app__chat--guion .app__answer{
  animation:app-ia-contesta 12s ease-out infinite;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
}
@keyframes app-ia-contesta{ 0%,29%{opacity:0} 31%,96%{opacity:1} 100%{opacity:0} }
.app__chat--guion p{
  animation-name:app-ia-linea;
  animation-duration:12s;
}
.app__chat--guion p:nth-of-type(1){ animation-delay:0s }
.app__chat--guion p:nth-of-type(2){ animation-delay:.7s }
@keyframes app-ia-linea{
  0%,30%  { opacity:0; translate:0 3px }
  35%,96% { opacity:1; translate:0 0 }
  100%    { opacity:0; translate:0 3px }
}

/* — El gráfico —
   Es la segunda mitad de la respuesta, no un adorno: la función de IA que lo
   justifica es la de generar informes con gráficos. Sube después del texto,
   que es el orden en que sale de verdad. Las barras escalan desde abajo con
   `transform-origin`, no cambiando el alto: así no recalculan la caja. */
.app__iachart{
  margin:9px 0 0;
  border:1px solid #2A2E34; border-radius:9px;
  padding:9px 11px 8px;
  background:rgba(255,255,255,.02);
}
.app__iachart figcaption{
  font-size:9.5px; color:#7C838C; margin-bottom:8px;
}
.app__iabars{ display:flex; align-items:flex-end; gap:7px; height:66px }
/* Rejilla y no columna flexible: el alto de la barra va en % y necesita una
   pista de alto definido contra la que medirse. En `flex-direction:column` el
   100 % era el de la caja entera —rótulo incluido—, así que la barra más alta
   se salía por arriba y las diferencias entre meses se aplastaban. */
.app__iabar{ flex:1; display:grid; grid-template-rows:1fr auto; gap:4px; height:100% }
.app__iabar i{
  display:block; align-self:end;
  height:calc(var(--v) * 100%);
  border-radius:3px 3px 0 0;
  background:linear-gradient(180deg,#C88BFA,#8B5CF6);
  transform-origin:50% 100%;
  scale:1 0;
  animation:app-ia-barra 12s cubic-bezier(.16,1,.3,1) infinite;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
}
.app__iabar:nth-child(2) i{ animation-delay:.08s }
.app__iabar:nth-child(3) i{ animation-delay:.16s }
.app__iabar:nth-child(4) i{ animation-delay:.24s }
.app__iabar:nth-child(5) i{ animation-delay:.32s }
.app__iabar:nth-child(6) i{ animation-delay:.40s }
@keyframes app-ia-barra{
  0%,42%  { scale:1 0 }
  54%,96% { scale:1 1 }
  100%    { scale:1 0 }
}
.app__iabar b{
  font-size:9px; font-weight:400; color:#7C838C; text-align:center;
}

/* — El segundo gráfico —
   Un informe no trae un solo gráfico: la propia capacidad lo promete
   («gráficos y conclusiones», en plural) y hasta el 30 de septiembre de 2026
   sólo se enseñaba uno. Éste sube DESPUÉS de que el primero termine de
   crecer, no a la vez —dos cosas apareciendo juntas se leen como una sola—.
   Es una barra apilada, no una segunda columna de barras: repetir la misma
   forma de gráfico dos veces en el mismo informe no demuestra variedad. */
.app__iachart--causas{ margin-top:8px; padding:8px 11px 7px }
.app__iastack{
  display:flex; height:10px; border-radius:5px; overflow:hidden;
  background:rgba(255,255,255,.06);
}
.app__iastackseg{
  flex:0 0 calc(var(--v) * 100%); min-width:0; height:100%;
  transform-origin:0 50%;
  scale:0 1;
  animation:app-ia-stack 12s cubic-bezier(.16,1,.3,1) infinite;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
}
.app__iastackseg[data-c="1"]{ background:linear-gradient(90deg,#8B5CF6,#C88BFA) }
.app__iastackseg[data-c="2"]{ background:#5A93E0 }
.app__iastackseg[data-c="3"]{ background:#3A3F46 }
.app__iastackseg:nth-child(2){ animation-delay:.06s }
.app__iastackseg:nth-child(3){ animation-delay:.12s }
@keyframes app-ia-stack{
  0%,54%  { scale:0 1 }
  64%,96% { scale:1 1 }
  100%    { scale:0 1 }
}
.app__ialeg{
  display:flex; flex-wrap:wrap; gap:4px 10px;
  margin:6px 0 0; padding:0; list-style:none;
  font-size:9px; color:#7C838C;
  opacity:0;
  animation:app-ia-leg 12s steps(1) infinite;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
}
@keyframes app-ia-leg{ 0%,56%{opacity:0} 66%,96%{opacity:1} 100%{opacity:0} }
.app__ialeg li{ display:flex; align-items:center; gap:4px; white-space:nowrap }
.app__ialeg i{ width:6px; height:6px; border-radius:50%; display:inline-block }
.app__ialeg li:nth-child(1) i{ background:linear-gradient(135deg,#8B5CF6,#C88BFA) }
.app__ialeg li:nth-child(2) i{ background:#5A93E0 }
.app__ialeg li:nth-child(3) i{ background:#3A3F46 }
/* Mismo motivo que el bloque de abajo para compartir/descargar: sin
   `!important` el reset general de movimiento reducido (más arriba en este
   fichero) pierde contra estas dos reglas, que van después con la misma
   especificidad. Sin esto la barra apilada quedaba en `scale:0 1` —ancho
   cero, invisible— y la leyenda en `opacity:0`. */
@media (prefers-reduced-motion:reduce){
  .app__iastackseg{ animation:none !important; scale:1 !important }
  .app__ialeg{ animation:none !important; opacity:1 !important }
}

/* — Más datos del informe: los KPI que cierran el análisis —
   El desglose de causas no es el último dato de un informe real: cierra con
   lo que le importa a quien lo pidió —coste, tiempo de resolución,
   disponibilidad—. Entra ya con el scroll empezado, no antes: es lo
   siguiente que aparece al seguir bajando, no una cuarta pieza que compite
   por atención con las dos primeras. */
.app__iakpis{
  display:flex; gap:1px; margin:9px 0 0;
  background:rgba(255,255,255,.07); border-radius:8px; overflow:hidden;
  opacity:0; translate:0 6px;
  animation:app-ia-kpis 12s ease-out infinite;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
}
.app__iakpi{
  flex:1; min-width:0; padding:7px 8px;
  background:#1B2026;
  display:flex; flex-direction:column; gap:2px;
}
.app__iakpi b{ font-size:12px; font-weight:500; color:#E7E9EC; white-space:nowrap }
.app__iakpi span{ font-size:8px; color:#7C838C; line-height:1.25 }
@keyframes app-ia-kpis{
  0%,64%  { opacity:0; translate:0 6px }
  74%,96% { opacity:1; translate:0 0 }
  100%    { opacity:0; translate:0 6px }
}

/* — Compartir y descargar —
   El cierre del informe: las dos salidas reales de un documento generado.
   Llega el último, cuando ya hay algo que compartir o descargar —antes de
   tiempo prometería una acción sobre un informe todavía a medias—. */
.app__iatoolbar{
  display:flex; gap:8px; margin:9px 0 0;
  opacity:0; translate:0 6px;
  animation:app-ia-tool 12s ease-out infinite;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
}
.app__iatool{
  display:inline-flex; align-items:center; gap:5px;
  padding:5px 10px; border-radius:7px;
  border:1px solid #33383F; color:#B9BEC6;
  font-size:9.5px; font-weight:500; white-space:nowrap;
}
.app__iatool svg{ width:11px; height:11px; flex:0 0 auto }
.app__iatool--pdf{
  background:linear-gradient(135deg,#8B5CF6,#C88BFA);
  border-color:transparent; color:#fff;
}
@keyframes app-ia-tool{
  0%,72%  { opacity:0; translate:0 6px }
  82%,96% { opacity:1; translate:0 0 }
  100%    { opacity:0; translate:0 6px }
}
/* Con movimiento reducido, el `opacity:0`/`translate:0 6px` de arriba —el
   fotograma de SALIDA de cada pieza— gana por orden de fuente: son la MISMA
   especificidad (0-1-0) que la del reset general de más arriba en este
   fichero, y esa regla va antes, así que aquí pierde para todo lo que no
   lleve `!important`. Con `!important` en los dos valores que importan
   —el resto del reset general tampoco lo necesita porque nada de lo suyo
   choca con una regla posterior de igual peso— quedan visibles, que es el
   fotograma que cuenta el informe entero. */
@media (prefers-reduced-motion:reduce){
  .app__iakpis,.app__iatoolbar{
    animation:none !important; opacity:1 !important; translate:0 0 !important;
  }
}

/* — El informe se lee entero, con scroll —
   Un párrafo y dos gráficos no caben siempre en la ventana del chat: el
   dispositivo es responsive (el marco escala con `cqw`, el contenido de
   dentro no), así que cuánto sobra cambia con el ancho y no hay un recorte
   fijo que valga en los nueve anchos de prueba del sistema (comprobado con
   Puppeteer, no a ojo: de -35px a -170px según el ancho). En vez de recortar
   el final del informe, `.app__answer--informe` cede el resto del chat a un
   visor de alto fijo (`.app__iascroll`) y es SU CONTENIDO el que se
   desplaza —mismo mecanismo que ya usa `.qrficha__scrollin` en
   `/gestion-de-activos/`, sólo que aquí el desplazamiento no es un píxel
   medido a mano: `container-type:size` en el visor deja usar `cqh` —el alto
   real y variable del hueco disponible— para que el `translate` sea siempre
   «lo que sobra», calculado por el navegador en cada ancho, no una cifra que
   haya que volver a medir si el contenido cambia. */
.app__answer--informe{ flex:1; min-height:0; align-items:stretch }
.app__iascroll{
  flex:1 1 auto; min-width:0; min-height:0; overflow:hidden; position:relative;
  container-type:size;
}
/* Con dos gráficos en el mismo informe, las barras de temperatura no
   necesitan el alto que les basta cuando van solas: más bajas dejan más
   hueco para el segundo gráfico sin perder la silueta de los seis meses. */
.app__answer--informe .app__iabars{ height:46px }
.app__iascrollin{
  animation:app-ia-scroll 12s ease-in-out infinite;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
}
@keyframes app-ia-scroll{
  0%,58%  { translate:0 0 }
  76%,92% { translate:0 min(0px, calc(100cqh - 100%)) }
  100%    { translate:0 0 }
}
@media (prefers-reduced-motion:reduce){
  /* Se queda en la posición desplazada, que es la que enseña el informe
     entero —los dos gráficos—, en vez de en la de arriba, que sólo enseña
     el primero. */
  .app__iascrollin{
    animation:none; translate:0 min(0px, calc(100cqh - 100%));
  }
}

/* Mismo estado resuelto, para cuando la capacidad del rotador ya terminó su
   vuelta única (ver la nota junto a .scene[data-terminado] en ds/scene.css). */
.scene[data-terminado] .app__iascrollin{ animation:none; translate:0 min(0px, calc(100cqh - 100%)); }

/* Los códigos que cita la respuesta son enlaces reales dentro de la app: es lo
   que separa "un chat" de "un chat sobre tus datos". */
.app__ref{ color:#7FB4FF; font-weight:500 }
.app__cursor{
  display:inline-block; width:6px; height:12px; vertical-align:-2px;
  background:#C88BFA; margin-left:2px;
  animation:app-cursor 1.1s steps(1) infinite;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
}
@keyframes app-cursor{ 0%,49%{opacity:1} 50%,100%{opacity:0} }
.app__prompt{
  margin-top:auto;
  display:flex; align-items:center; gap:8px;
  border:1px solid #33383F; border-radius:9px;
  padding:7px 10px;
  font-size:11px; color:#7C838C;
}
.app__prompt svg{ width:14px; height:14px; flex:0 0 auto; color:#C88BFA }

/* =========================================================
   PANTALLA DE INTEGRACIONES
   Solved en el centro y alrededor lo que ya hay en la fábrica. La rejilla es
   deliberadamente sobria: aquí el argumento son los logos, no el dibujo.
   ========================================================= */
.app__integra{
  flex:1; min-height:0;
  padding:16px;
  display:grid;
  grid-template-columns:repeat(4,1fr);
  gap:10px;
  align-content:center;
  min-width:var(--app-integra-w,0);
}
.app__int{
  display:grid; place-items:center;
  height:52px;
  background:#fff;
  border:1px solid var(--app-line);
  border-radius:9px;
  transition:border-color var(--ds-motion);
}
.app__int img{ max-width:66%; max-height:24px; width:auto; height:auto; object-fit:contain }
/* La casilla de Solved manda: es la que recibe y reparte. */
.app__int--solved{
  border-color:var(--app-blue);
  box-shadow:0 0 0 3px rgba(30,107,255,.10);
}
.app__intcap{
  grid-column:1/-1;
  margin:2px 0 0;
  font-size:10.5px; line-height:1.4; color:var(--app-text-2);
}
.app__intcap b{ font-weight:500; color:var(--app-text) }

/* --- Acciones: el listado cambia de filtro -------------------------------
   Dos vistas del mismo listado que se relevan: primero filtrado por equipo y
   después por persona. Es lo que hace un responsable de planta cada mañana, y
   es lo que la tarjeta cuenta — que las tareas tienen dueño y se pueden mirar
   por donde haga falta.

   Van superpuestas en la misma celda de rejilla, no una debajo de otra: así la
   tabla no da un salto de alto al cambiar el filtro, que es lo que delataría
   que son dos cosas distintas y no un filtro. */
.app__table tbody tr{
  animation:app-row-in 8s ease-out infinite;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
}
.app__table tbody tr:nth-child(2){ animation-delay:.12s }
.app__table tbody tr:nth-child(3){ animation-delay:.24s }
.app__table tbody tr:nth-child(4){ animation-delay:.36s }
.app__table tbody tr:nth-child(5){ animation-delay:.48s }
@keyframes app-row-in{
  0%,2%   { opacity:0 }
  9%,96%  { opacity:1 }
  100%    { opacity:0 }
}

/* El relevo del estado. Se marca con data-swap para no animar todos los chips
   de la tabla: sólo el de la acción que se cierra. */
.app__swap{ position:relative; display:inline-grid }
.app__swap > *{ grid-area:1/1 }
.app__swap > :first-child{
  animation:app-swap-out 8s ease-in-out infinite;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
}
.app__swap > :last-child{
  animation:app-swap-in 8s ease-in-out infinite;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
}
@keyframes app-swap-out{ 0%,58%{opacity:1} 66%,100%{opacity:0} }
@keyframes app-swap-in { 0%,58%{opacity:0} 66%,100%{opacity:1} }

/* --- Integraciones: los sistemas se van conectando -----------------------
   Cada casilla aparece con su retraso —el orden en que se enchufa un ERP, un
   MES, el correo— y la de Solved late al recibirlos. El movimiento aquí es el
   argumento: sin él, la rejilla es un muro de logos. */
.app__int{
  animation:app-int-in 9s ease-out infinite;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
}
.app__int:nth-child(2){ animation-delay:.14s }
.app__int:nth-child(3){ animation-delay:.28s }
.app__int:nth-child(4){ animation-delay:.42s }
.app__int:nth-child(5){ animation-delay:.56s }
.app__int:nth-child(7){ animation-delay:.70s }
.app__int:nth-child(8){ animation-delay:.84s }
@keyframes app-int-in{
  0%,3%   { opacity:0; scale:.97 }
  10%,96% { opacity:1; scale:1 }
  100%    { opacity:0; scale:.97 }
}
/* La casilla de Solved no aparece: ya estaba. Lo que hace es recibir. */
.app__int--solved{
  animation:app-int-hub 9s ease-out infinite;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
}
@keyframes app-int-hub{
  0%,12%  { box-shadow:0 0 0 3px rgba(30,107,255,.10) }
  24%     { box-shadow:0 0 0 7px rgba(30,107,255,.16) }
  40%,100%{ box-shadow:0 0 0 3px rgba(30,107,255,.10) }
}

@media (prefers-reduced-motion:reduce){
  .app__table tbody tr,.app__swap>*,.app__int,.app__int--solved{
    animation:none !important; opacity:1; scale:1;
  }
  .app__swap > :first-child{ opacity:0 }
}

/* Mismo estado resuelto, para cuando la capacidad del rotador ya terminó su
   vuelta única (ver la nota junto a .scene[data-terminado] en ds/scene.css). */
.scene[data-terminado] .app__table tbody tr,
.scene[data-terminado] .app__swap>*,
.scene[data-terminado] .app__int,
.scene[data-terminado] .app__int--solved{ animation:none !important; opacity:1; scale:1; }
.scene[data-terminado] .app__swap > :first-child{ opacity:0 }

/* =========================================================
   ESQUEMA DE CONEXIÓN
   La única pieza del fragmento que NO es sólo una pantalla, y va aquí
   documentada como excepción para que nadie la copie sin pensarlo.

   Cuenta un ciclo entero, no una foto: de tres orígenes —un sensor de planta,
   el ERP y una máquina— baja un dato por su cable, y al llegar al móvil
   APARECE LA INCIDENCIA que ese dato ha creado. La pantalla del teléfono es la
   de siempre (.app--movil), así que la mitad de abajo sigue siendo producto de
   verdad; lo que se añade arriba es de dónde viene.

   Por eso se justifica el esquema: una captura del listado no dice "esto ha
   entrado solo, desde el sensor". El movimiento ES el argumento.

   SINCRONÍA. Todo comparte un ciclo de 12s y se desfasa con animation-delay:
     origen A  el dato baja de 0% a 12%   → su incidencia aparece en el 12%
     origen B  delay 4s  (33% → 45%)
     origen C  delay 8s  (66% → 78%)
   Las tres incidencias se acumulan y se van al cerrar el ciclo. Si se toca la
   duración hay que tocarla en los cinco sitios o la bolita llegará cuando la
   incidencia ya esté puesta, que es lo único que rompe el efecto.
   ========================================================= */
/* El esquema NO es una pantalla, así que no lleva marco: sin fondo, sin radio
   y sin la sombra que .scene__media le pone a los fragmentos. Con ellos
   parecía una ventana de navegador flotando dentro de la tarjeta, que es
   justo lo que el componente prohíbe. Flota sobre el lienzo teñido y ya. */
.scene__media:has(> .hub){
  box-shadow:none;
  border-radius:0;
  overflow:visible;
}
.hub{
  position:relative;
  width:100%; height:100%;
  background:transparent;
  font-family:Roboto,'Helvetica Neue',Arial,sans-serif;
  container-type:size;
}

/* --- Los cables ---------------------------------------------------------- */
.hub__wires{ position:absolute; inset:0; width:100%; height:100%; display:block }
.hub__wire{
  fill:none;
  stroke:var(--app-line-2,#C7CFDD);
  stroke-width:1.6;
  stroke-dasharray:5 5;
  stroke-linecap:round;
}
/* El dato que baja. El dashoffset va a negativo para que avance en el sentido
   en que está trazado el path —del origen al móvil—; en positivo el patrón
   retrocede y el dato subiría, que cuenta lo contrario.

   POR QUÉ `pathLength="100"` EN EL MARCADO Y NO LONGITUDES REALES (8 sep 2026,
   dos fallos que se veían como uno solo):

   1. La bolita se veía PARADA en su burbuja antes de salir. `animation-delay`
      no pinta el fotograma 0 % mientras espera —el `fill-mode` es `none`—, así
      que durante esos 4 y 8 segundos el cable se pintaba con el valor propio
      del elemento, y no había ninguno: el `stroke-dashoffset:0` por defecto es
      el guión al principio del trazo, o sea la bolita puesta en el origen sin
      haber salido. Ahora el valor en reposo es -1000, fuera del trazo.
   2. Y el -420 con el que la animación «apartaba» la bolita el resto del ciclo
      TAMPOCO la apartaba: el patrón medía 14+400=414, así que a -420 el guión
      volvía a entrar por el principio —420-414=6— y la bolita reaparecía justo
      debajo de la burbuja. El hueco tiene que ser mayor que cualquier
      desplazamiento que se use, de ahí el 1000.

   Con `pathLength="100"` los tres cables miden lo mismo para el CSS —de verdad
   miden 92,9 y 46—, así que un solo juego de fotogramas sirve para los tres y
   la bolita tarda lo mismo en recorrerlos. Antes el -300 era tres veces el
   cable: la bolita llegaba al teléfono en 0,45 s y la incidencia nacía en
   1,32 s, casi un segundo después. Ahora llega en el 12 %, que es cuando la
   ficha empieza a abrirse. */
.hub__pulse{
  fill:none;
  stroke:var(--app-blue,#1E6BFF);
  stroke-width:2.6;
  stroke-linecap:round;
  stroke-dasharray:5 1000;
  stroke-dashoffset:-1000;
  animation:hub-pulse 12s linear infinite;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
  filter:drop-shadow(0 0 3px rgba(30,107,255,.6));
}
.hub__pulse--b{ animation-delay:4s }
.hub__pulse--c{ animation-delay:8s }
@keyframes hub-pulse{
  0%          { stroke-dashoffset:0 }
  12%         { stroke-dashoffset:-100 }   /* el 100 es el cable entero */
  12.01%,100% { stroke-dashoffset:-1000 }  /* fuera del cable el resto del ciclo */
}

/* --- Los orígenes -------------------------------------------------------- */
.hub__node{
  position:absolute;
  translate:-50% -50%;
  display:grid;
  place-items:center;
  width:11.4cqw; height:11.4cqw;
  border-radius:50%;
  background:#fff;
  border:1px solid var(--app-line,#E6EAF2);
  box-shadow:0 2px 10px rgba(12,32,64,.06);
  color:var(--app-text-2,#4A5468);
}
.hub__node svg{ width:44%; height:44%; display:block }
/* Las fotos recortadas llenan más que un logo: el objeto ya viene ajustado a
   su caja, así que sin este 72% quedaría flotando en medio de la burbuja. El
   logo del ERP es apaisado y se queda en 58%. */
.hub .hub__node img{ width:72%; height:auto; object-fit:contain }
.hub .hub__node--b img{ width:58% }
/* Los iconos del brandbook entran por `.bico` (máscara + currentColor), no como
   <img>: así toman el gris de la burbuja. Miden lo que un svg en línea. */
.hub .hub__node .bico{ width:44%; height:44% }
/* Late justo cuando suelta su dato: es lo que ata la burbuja con la bolita. */
.hub__node{
  animation:hub-node 12s ease-out infinite;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
}
.hub__node--b{ animation-delay:4s }
.hub__node--c{ animation-delay:8s }
@keyframes hub-node{
  0%      { box-shadow:0 0 0 0 rgba(30,107,255,.45), 0 2px 10px rgba(12,32,64,.06) }
  7%      { box-shadow:0 0 0 8px rgba(30,107,255,0), 0 2px 10px rgba(12,32,64,.06) }
  8%,100% { box-shadow:0 0 0 0 rgba(30,107,255,0), 0 2px 10px rgba(12,32,64,.06) }
}
/* La etiqueta se separa con la misma unidad que fija el diámetro: en % se
   despegaría en cuanto la tarjeta cambiara de proporción. */
/* La etiqueta se separa con la misma unidad que fija el diámetro: en % se
   despegaría en cuanto la tarjeta cambiara de proporción.

   PARTE EN VARIAS LÍNEAS, y no es un descuido de `nowrap` (8 sep 2026). Los
   rótulos pasaron de una palabra —«Máquina», «ERP»— a nombrar el canal entero
   —«Correo de proveedor/cliente»—, y en una sola línea los tres se montaban
   unos encima de otros hasta ser ilegibles. El ancho es el de la calle entre
   burbuja y burbuja, así que dos etiquetas no pueden tocarse por definición.
   El cuerpo escala con la tarjeta y va acotado por los dos lados: sin el
   mínimo, en la escena estrecha bajaba de 8px. */
.hub__tag{
  position:absolute;
  translate:-50% 0;
  margin-top:6.8cqw;
  width:30cqw;
  text-align:center;
  text-wrap:balance;
  font-size:clamp(8.5px, 3.2cqw, 11px); line-height:1.25;
  color:var(--app-text-2,#4A5468);
}

/* --- El móvil ------------------------------------------------------------ */
/* container-type propio: ds/device.css calcula el bisel y el radio del iPhone
   en cqw, y sin esto los mide contra el ancho del hub —cuatro veces mayor— y
   el teléfono sale con esquinas de 120px. El contenedor tiene que ser el padre
   directo del .device, como dice el propio componente. */
.hub__phone{
  position:absolute;
  translate:-50% 0;
  width:34cqw;
  container-type:inline-size;
}

/* — EL ESQUEMA EN UNA ESCENA ESTRECHA (8 sep 2026) —
   Al pasar la tarjeta de incidencias a cuatro columnas, el hub bajó de 616 a
   272px y con él todo lo que mide en `cqw`: el teléfono se quedó en 92px y las
   fichas de incidencia, ilegibles. Aquí no vale encoger proporcionalmente,
   porque lo que hay que leer —la ficha que nace— tiene un tamaño mínimo por
   debajo del cual la escena deja de demostrar nada. Así que el teléfono y las
   burbujas ocupan MÁS proporción de la caja, no menos.

   La consulta es sobre el propio `.hub`, que ya es contenedor de tamaño; el
   umbral está por encima del ancho de la estrecha (272px) y por debajo del de
   la ancha (616px). */
@container (max-width: 400px){
  .hub__phone{ width:68cqw }
  .hub__node{ width:16cqw; height:16cqw }
  .hub__tag{ margin-top:9.5cqw; width:32cqw }
}

/* Las incidencias que van naciendo. Cada una entra cuando su bolita toca el
   teléfono y se queda hasta que el ciclo cierra. */
.hub__new{
  overflow:hidden;
  animation:hub-new-a 12s ease-out infinite;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
}
.hub__new--b{ animation-name:hub-new-b }
.hub__new--c{ animation-name:hub-new-c }
/* No basta con la opacidad ni con el alto: una tarjeta colapsada conserva su
   relleno y su borde —16px y 2px por cabeza—, y tres de ellas dejan un palmo
   de hueco sobre el listado. Colapsan en alto y el margen negativo se
   come el gap de la lista, así que el listado CRECE cuando llega el dato —que
   es justo lo que se está contando—. */
@keyframes hub-new-a{
  0%,11%  { opacity:0; max-height:0; padding-block:0; border-width:0; margin-bottom:-7px }
  14%,96% { opacity:1; max-height:110px; padding-block:8px; border-width:1px; margin-bottom:0 }
  100%    { opacity:0; max-height:0; padding-block:0; border-width:0; margin-bottom:-7px }
}
@keyframes hub-new-b{
  0%,44%  { opacity:0; max-height:0; padding-block:0; border-width:0; margin-bottom:-7px }
  47%,96% { opacity:1; max-height:110px; padding-block:8px; border-width:1px; margin-bottom:0 }
  100%    { opacity:0; max-height:0; padding-block:0; border-width:0; margin-bottom:-7px }
}
@keyframes hub-new-c{
  0%,77%  { opacity:0; max-height:0; padding-block:0; border-width:0; margin-bottom:-7px }
  80%,96% { opacity:1; max-height:110px; padding-block:8px; border-width:1px; margin-bottom:0 }
  100%    { opacity:0; max-height:0; padding-block:0; border-width:0; margin-bottom:-7px }
}
/* El contador de abiertas sube con ellas. Tres cifras superpuestas que se
   relevan: así el número no salta de ancho al cambiar. */
.hub__count{ display:inline-grid; vertical-align:baseline }
.hub__count > *{ grid-area:1/1 }
.hub__count > :nth-child(1){ animation:hub-n1 12s steps(1) infinite }
.hub__count > :nth-child(2){ animation:hub-n2 12s steps(1) infinite }
.hub__count > :nth-child(3){ animation:hub-n3 12s steps(1) infinite }
.hub__count > *{ animation-play-state:var(--play,running); animation-iteration-count:var(--iter,infinite); animation-fill-mode:forwards; }
@keyframes hub-n1{ 0%,13%{opacity:1} 14%,100%{opacity:0} }
@keyframes hub-n2{ 0%,13%{opacity:0} 14%,46%{opacity:1} 47%,100%{opacity:0} }
@keyframes hub-n3{ 0%,46%{opacity:0} 47%,100%{opacity:1} }

@media (prefers-reduced-motion:reduce){
  .hub__pulse{ animation:none; stroke-dashoffset:-50 }
  .hub__node,.hub__new,.hub__count > *{ animation:none; opacity:1; max-height:none; margin-bottom:0 }
  .hub__count > :nth-child(1),.hub__count > :nth-child(2){ opacity:0 }
}

/* Mismo estado resuelto, para cuando la capacidad del rotador ya terminó su
   vuelta única (ver la nota junto a .scene[data-terminado] en ds/scene.css). */
.scene[data-terminado] .hub__pulse{ animation:none; stroke-dashoffset:-50 }
.scene[data-terminado] .hub__node,
.scene[data-terminado] .hub__new,
.scene[data-terminado] .hub__count > *{ animation:none; opacity:1; max-height:none; margin-bottom:0 }
.scene[data-terminado] .hub__count > :nth-child(1),
.scene[data-terminado] .hub__count > :nth-child(2){ opacity:0 }

/* =========================================================
   EL PUENTE — cómo se conecta Solved con el sistema que ya tienes
   Va en la tarjeta ampliada de Integraciones. Réplica del recurso de la
   tarjeta «Integra pagos en tu plataforma» de la home de Stripe, medido en su
   DOM en vivo el 16 de agosto de 2026.

   LO QUE SE COPIA (que es la composición, no el contenido):
     · dos planos — la ventana de la plataforma DETRÁS, y delante una tarjeta
       pequeña, elevada, del sistema con el que se habla;
     · un cable explícito ENTRE los dos, en codo: sube, gira con una esquina
       redondeada y sale en horizontal. Es lo que convierte dos pantallas
       sueltas en «una habla con la otra»;
     · el cable es PUNTEADO y muy fino —punto y hueco iguales—, no la raya de
       5-5 de `.hub__wire`: aquí no es una manguera de datos, es un enlace;
     · el trazo va en degradado y se APAGA en el extremo lejano, así el cable
       no compite con las dos pantallas ni parece un borde;
     · y se dibuja solo, de un extremo al otro.

   Sus proporciones son las de Stripe, en porcentaje sobre un lienzo de
   1000×457: ventana 72,8 % de ancho a (21,6 %, 5,7 %); tarjeta 29,6 % a
   (7 %, 24,1 %); cable 13,1 % a (10,2 %, 19,3 %).

   LO QUE NO SE COPIA: el sentido. El de Stripe va sólo hacia la plataforma;
   aquí el pulso hace el viaje de ida y vuelta, porque la integración de Solved
   es en los dos sentidos y es lo que dice el propio diálogo.
   ========================================================= */
.bridge{ position:absolute; inset:0 }

/* La ventana, detrás y desplazada a la derecha. Se sale por abajo a propósito:
   es la única pieza del puente que se recorta, y es lo que da profundidad.
   Lleva además `device-box` en el marcado: es el contenedor contra el que
   ds/device.css calcula el bisel y los radios en `cqw`. */
.bridge__win{
  position:absolute;
  left:21.6%; top:5.7%; width:72.8%;
}

/* La tarjeta del sistema con el que se habla: delante, a la izquierda y
   elevada. La sombra es la de Stripe, convertida a los tokens de aquí. */
.bridge__card{
  position:absolute; z-index:2;
  left:7%; top:24.1%; width:29.6%;
  background:#fff;
  border:1px solid var(--app-line);
  border-radius:10px;
  box-shadow:0 16px 32px rgba(50,50,93,.12);
  padding:11px 12px 12px;
  display:flex; flex-direction:column; gap:9px;
}
.bridge__cardh{ display:flex; align-items:center; justify-content:space-between; gap:8px }
.bridge .bridge__logo{ height:18px; width:auto; display:block }
.bridge__badge{
  display:inline-flex; align-items:center; gap:4px;
  font-size:9px; font-weight:500; color:var(--app-ok);
}
.bridge__badge::before{
  content:""; width:5px; height:5px; border-radius:50%; background:currentColor;
}
.bridge__cardt{ margin:0; font-size:10px; color:var(--app-mute) }
.bridge__log{ list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:6px }
.bridge__log li{
  display:flex; align-items:center; justify-content:space-between; gap:8px;
  padding-top:6px; border-top:1px solid var(--app-line);
  font-size:9.5px; color:var(--app-text-2);
}
.bridge__log li:first-child{ padding-top:0; border-top:0 }
.bridge__ok{ color:var(--app-ok) }
.bridge__wait{ color:var(--app-mute) }
.bridge__foot{
  margin:0; padding-top:8px; border-top:1px solid var(--app-line);
  font-size:9px; color:var(--app-mute);
}

/* --- El cable ------------------------------------------------------------
   Entre los dos planos: por encima de la ventana y por debajo de la tarjeta,
   que es lo que hace que parezca entrar por detrás de ésta. */
.bridge__wire{
  position:absolute; z-index:1;
  left:10.2%; top:21%; width:11.4%;
  height:auto; overflow:visible; display:block;
}
.bridge__trace,.bridge__pulse2{ fill:none; stroke-linecap:round }
/* Punto y hueco iguales, y el punto vale 1,33 veces el grosor: así se lee como
   una línea de puntos y no como una raya cortada. */
/* — EL CABLE EN LA CARÁTULA DE LA HOME (.bridge__wire--cara) —
   Dos arreglos, y los dos vienen de que el puente se copió a una caja con otra
   proporción que la del diálogo:

   1. El degradado se referencia POR IDENTIFICADOR. Dos <defs> con el mismo id
      en un documento resuelven los dos al primero, así que la copia lleva los
      suyos (`…-cara`) y aquí se le dice de dónde pinta. Sin esta línea el cable
      se queda sin trazo: existe, ocupa su caja y no se ve.
   2. Y se le cambia la geometría. El cable mide el alto a partir de su ANCHO
      —`width` en % y `height:auto`, que es como se comporta un SVG—, mientras
      que la tarjeta del ERP mide su `top` contra el ALTO del contenedor. En el
      diálogo la caja es más alta y las dos medidas se encuentran; en la
      carátula, más ancha y más baja, el cable creció y se hundió detrás de la
      tarjeta hasta desaparecer entero. Sube y se ensancha para volver a salir
      por encima de ella y morir dentro de la ventana, que es lo que cuenta la
      pieza: el dato sale de Solved y entra en el ERP. */
.bridge__wire--cara{ left:9%; top:19%; width:12.6% }
.bridge__wire--cara .bridge__trace{ stroke:url(#bridge-grad-cara); stroke-width:1.5 }
.bridge__wire--cara .bridge__pulse2{ stroke-width:2.8 }
.bridge__trace{
  stroke:url(#bridge-grad);
  stroke-width:1.2;
  stroke-dasharray:1.6 1.6;
  /* EL CABLE SE MUEVE, Y LO HACE DESFILANDO SUS PROPIOS PUNTOS.
     El desplazamiento va de 0 a -3,2, que es exactamente un punto más un hueco:
     el ciclo cierra donde empieza y no hay salto. Se anima sobre el trazado
     VISIBLE —no dentro del <mask>, que es lo que Chrome no repinta— así que
     esto sí se ve. El sentido es el del trazado: de la ficha del ERP hacia la
     ventana de Solved, el mismo que recorre la bolita. */
  animation:bridge-flow 2.4s linear infinite;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
}
@keyframes bridge-flow{ to{ stroke-dashoffset:-3.2 } }
/* EL CABLE YA NO SE DIBUJA SOLO, Y NO ES UNA DECISIÓN DE GUSTO (8 sep 2026).

   Estaba montado así: el trazo punteado va dentro de un <g> enmascarado, y lo
   que se animaba era el `stroke-dashoffset` de un segundo trazado —blanco, sin
   puntear— dentro del <mask>. La idea era buena: animar el offset del propio
   punteado hace DESFILAR los puntos, que cuenta otra cosa.

   El problema es que no funcionaba: **Chrome no vuelve a pintar una máscara SVG
   cuando lo que se anima está DENTRO de ella**. La máscara se quedaba en su
   primer fotograma —offset 222, o sea nada revelado— para siempre, así que el
   cable era invisible durante todo el ciclo. Comprobado el 8 de septiembre de
   2026 con el cable de la carátula y con el del diálogo `u-integra`: los dos
   llevaban meses sin verse. Con `stroke-dashoffset:0` fijo aparece entero, y
   quitando el `mask` del <g> también.

   Así que el cable se queda PUESTO y quieto —es un cable, existe— y la
   animación la lleva la bolita que lo recorre, que es la que dice lo que hay
   que decir: el dato sale de Solved y entra en el ERP. Esa sí se repinta,
   porque se anima sobre el trazado visible y no dentro de una máscara.

   La máscara se queda en el marcado, abierta del todo: no cuesta nada y deja
   escrito dónde iba el efecto. Si algún día se quiere de vuelta, la salida NO
   es reanimarla —Chrome sigue igual—, es un `clip-path` en el propio elemento,
   que sí invalida. */
.bridge__mask{
  fill:none; stroke:#fff; stroke-width:4; stroke-linecap:round;
  stroke-dasharray:222;
  stroke-dashoffset:0;
}
/* EL CONECTOR — donde el cable toca la pantalla, y donde se queda.
   El cable llegaba hasta DENTRO de la ventana, cruzando por encima de la
   interfaz: un cable no atraviesa una pantalla, se enchufa a su borde. El ancho
   del SVG se ajusta para que su extremo caiga sobre el canto izquierdo del
   aparato —12,6 % en la carátula y 11,4 % en el diálogo, que son las dos cajas
   con proporciones distintas— y aquí se remata con el conector.

   Va en el extremo del trazado (131,1), que es el borde derecho del viewBox: el
   SVG lleva `overflow:visible`, así que la mitad del punto queda por fuera y se
   lee apoyado en el canto, no pegado por dentro.

   El halo NO late todo el rato: se enciende cuando llega el dato. Comparte los
   9 s del pulso y abre en el 46 %, que es el fotograma en el que la bolita
   alcanza la ventana, y se apaga cuando emprende la vuelta. */
/* Baja lo justo para que el conector caiga en el cuerpo de la ventana y no
   sobre la banda azul de la cabecera, donde un punto azul no se ve. Y aun así
   lleva un filo blanco: si algún día cambia el alto de la cabecera, el conector
   se sigue leyendo sobre azul, sobre gris y sobre blanco. */
.bridge__plug{ fill:var(--app-blue); stroke:#fff; stroke-width:1 }
.bridge__halo{
  fill:var(--app-blue);
  opacity:0;
  transform-box:fill-box; transform-origin:center;
  animation:bridge-plug 9s ease-in-out infinite;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
}
@keyframes bridge-plug{
  0%,40%   { transform:scale(.5); opacity:0 }
  46%,58%  { transform:scale(1);  opacity:.26 }
  72%,100% { transform:scale(.5); opacity:0 }
}

/* El dato que viaja. Ida y vuelta: sube al ERP y baja a la ficha. */
.bridge__pulse2{
  stroke:var(--app-blue);
  stroke-width:2.2;
  stroke-dasharray:9 300;
  animation:bridge-pulse 9s ease-in-out infinite;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
  filter:drop-shadow(0 0 3px rgba(30,107,255,.55));
}
@keyframes bridge-pulse{
  0%,24%  { stroke-dashoffset:0 }
  46%     { stroke-dashoffset:-231 }   /* llega a la ventana */
  58%     { stroke-dashoffset:-231 }   /* espera allí */
  80%     { stroke-dashoffset:0 }      /* y vuelve a la ficha */
  100%    { stroke-dashoffset:0 }
}

@media (prefers-reduced-motion:reduce){
  .bridge__pulse2{ animation:none }
  .bridge__pulse2{ opacity:0 }
  /* Ni los puntos desfilan ni el halo late. El conector se queda puesto y
     encendido: es el estado, no el movimiento, lo que cuenta. */
  .bridge__trace{ animation:none; stroke-dashoffset:0 }
  .bridge__halo{ animation:none; opacity:.26 }
}

/* Mismo estado resuelto, para cuando la capacidad del rotador ya terminó su
   vuelta única (ver la nota junto a .scene[data-terminado] en ds/scene.css). */
.scene[data-terminado] .bridge__pulse2{ animation:none }
.scene[data-terminado] .bridge__pulse2{ opacity:0 }
.scene[data-terminado] .bridge__trace{ animation:none; stroke-dashoffset:0 }
.scene[data-terminado] .bridge__halo{ animation:none; opacity:.26 }

/* Por debajo de tablet el puente no cabe: la tarjeta de delante taparía media
   ventana. Se apilan, y el cable —que sólo tiene sentido entre dos planos que
   se solapan— desaparece. */
@media (max-width:719px){
  .bridge{ position:static; display:flex; flex-direction:column; gap:14px }
  .bridge__win,.bridge__card{ position:static; width:100%; left:auto; top:auto }
  .bridge__wire{ display:none }
}
/* EL PUENTE EN LA CARÁTULA DE LA HOME, EN TELÉFONO (15 sep 2026). Apilar vale
   para el diálogo, que se desplaza; en la escena no: el fragmento se recorta
   por el borde inferior, así que de la pila sólo asomaba la ventana y la
   ficha del ERP —que es lo que la tarjeta promete— se quedaba fuera de la
   caja. «Integración con los sistemas ya implantados» se leía como una
   captura cualquiera de Solved. Aquí se mantienen los dos planos, con la
   ficha delante y abajo a la izquierda, tapando la barra lateral de la
   ventana —lo prescindible— y dejando la cabecera y las primeras filas de
   incidencias a la vista, que son las que la ficha repite como partes creados.
   Ficha y ventana comparten identificadores: eso es lo que hay que ver.
   El cable sigue fuera: a este tamaño no hay sitio para el codo, y dos
   planos que se solapan ya se leen como «una habla con la otra». */
@media (max-width:719px){
  .scene .bridge{ position:absolute; inset:0; display:block }
  .scene .bridge__win{ position:absolute; left:24%; top:0; width:100% }
  .scene .bridge__card{ position:absolute; left:0; top:21%; width:60%; max-width:260px }
}

.app__views{ display:grid; flex:1; min-height:0 }
.app__views > *{ grid-area:1/1 }
.app__view{
  display:flex; flex-direction:column; gap:10px; min-height:0;
  animation:app-view-a 12s ease-in-out infinite;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
}
.app__view--b{ animation-name:app-view-b }
.app__view--c{ animation-name:app-view-c }
/* Cada vista manda la mitad del ciclo, con un cruce corto: el relevo tiene
   que leerse como un filtro que cambia, no como un parpadeo. */
/* Tres pasos de filtro, cuatro segundos cada uno. El cruce es corto: tiene que
   leerse como una lista que se acorta, no como un pase de diapositivas. */
@keyframes app-view-a{ 0%,28%{opacity:1} 33%,95%{opacity:0} 100%{opacity:1} }
@keyframes app-view-b{ 0%,28%{opacity:0} 33%,61%{opacity:1} 66%,100%{opacity:0} }
@keyframes app-view-c{ 0%,61%{opacity:0} 66%,95%{opacity:1} 100%{opacity:0} }

/* La barra de filtros. Los chips se acumulan, así que puede haber dos o tres:
   se envuelven antes que encogerse, porque un chip recortado deja de decir por
   qué está filtrando. */
.app__filters{
  display:flex; align-items:center; gap:6px; flex-wrap:wrap;
  flex:0 0 auto;
}


/* El chip que dice por qué se está filtrando. */
.app__filter{
  display:inline-flex; align-items:center; gap:6px;
  padding:5px 10px;
  border:1px solid var(--app-line);
  border-radius:999px;
  background:#fff;
  font-size:10.5px; font-weight:500; color:var(--app-text-2);
  white-space:nowrap;
}
.app__filter{ overflow:hidden }
.app__filter svg{ width:12px; height:12px; flex:0 0 auto; color:var(--app-mute) }
.app__filter .app__ava{ width:16px; height:16px; font-size:9px }
/* La flecha de desplegable: sin ella el chip parece una etiqueta y no un
   filtro que se puede abrir. */
.app__fcar{ opacity:.55 }

/* El valor elegido. Arranca colapsado —el filtro está vacío— y entra al
   seleccionarlo. Se anima el ancho y no sólo la opacidad: si sólo se
   desvaneciera, el hueco ya estaría reservado y no se leería como que el
   valor ENTRA en el filtro. */
.app__fval{
  display:inline-flex; align-items:center;
  max-width:0; opacity:0; overflow:hidden;
  white-space:nowrap;
  animation-duration:12s; animation-iteration-count:infinite;
  animation-timing-function:cubic-bezier(.16,1,.3,1);
  animation-play-state:var(--play,running);
}
.app__filter--dep .app__fval{ animation-name:app-val-dep }
.app__filter--resp .app__fval{ animation-name:app-val-resp }
@keyframes app-val-dep{
  0%,28%  { max-width:0;    opacity:0 }
  36%,95% { max-width:16ch; opacity:1 }
  100%    { max-width:0;    opacity:0 }
}
@keyframes app-val-resp{
  0%,61%  { max-width:0;    opacity:0 }
  69%,95% { max-width:12ch; opacity:1 }
  100%    { max-width:0;    opacity:0 }
}
/* El avatar entra con el nombre: es parte del valor, no del campo. */
.app__fval--ava{ margin-right:0 }
.app__filter--resp .app__fval--ava{ animation-name:app-val-ava }
@keyframes app-val-ava{
  0%,61%  { max-width:0;  opacity:0; margin-right:0 }
  69%,95% { max-width:20px; opacity:1; margin-right:6px }
  100%    { max-width:0;  opacity:0; margin-right:0 }
}
/* Filtrar por persona es más concreto que por equipo, y el chip lo dice. */
.app__filter--on{
  background:var(--app-blue-wash);
  border-color:rgba(30,107,255,.28);
  color:var(--app-blue-deep);
}

/* --- Los botones se seleccionan solos ------------------------------------
   La barra se queda fija y lo que cambia es el ESTADO de cada botón: primero
   se enciende el de departamento y luego el de responsable. Así se ve el gesto
   —qué se pulsa y qué pasa— en vez de un baile de chips que aparecen y
   desaparecen, que es lo que hacía antes y no contaba nada.

   El anillo que se abre en el instante del cambio es la mitad del efecto: sin
   él, el botón sólo cambia de color y no se lee como una pulsación. */
.app__filter--dep,
.app__filter--resp{
  animation-duration:12s;
  animation-iteration-count:infinite;
  animation-timing-function:ease-out;
  animation-play-state:var(--play,running);
}
.app__filter--dep{ animation-name:app-pick-dep }
.app__filter--resp{ animation-name:app-pick-resp }

@keyframes app-pick-dep{
  0%,28%   { background:#fff; border-color:var(--app-line); color:var(--app-text-2);
             box-shadow:0 0 0 0 rgba(30,107,255,0) }
  33%      { background:var(--app-blue-wash); border-color:rgba(30,107,255,.28); color:var(--app-blue-deep);
             box-shadow:0 0 0 5px rgba(30,107,255,.22) }
  40%,95%  { background:var(--app-blue-wash); border-color:rgba(30,107,255,.28); color:var(--app-blue-deep);
             box-shadow:0 0 0 0 rgba(30,107,255,0) }
  100%     { background:#fff; border-color:var(--app-line); color:var(--app-text-2);
             box-shadow:0 0 0 0 rgba(30,107,255,0) }
}
@keyframes app-pick-resp{
  0%,61%   { background:#fff; border-color:var(--app-line); color:var(--app-text-2);
             box-shadow:0 0 0 0 rgba(30,107,255,0) }
  66%      { background:var(--app-blue-wash); border-color:rgba(30,107,255,.28); color:var(--app-blue-deep);
             box-shadow:0 0 0 5px rgba(30,107,255,.22) }
  73%,95%  { background:var(--app-blue-wash); border-color:rgba(30,107,255,.28); color:var(--app-blue-deep);
             box-shadow:0 0 0 0 rgba(30,107,255,0) }
  100%     { background:#fff; border-color:var(--app-line); color:var(--app-text-2);
             box-shadow:0 0 0 0 rgba(30,107,255,0) }
}

/* El contador acompaña al filtro: 9 → 3 → 2. Tres cifras superpuestas, para
   que el número no salte de ancho al cambiar. */
.app__cntswap{ margin-left:auto; display:inline-grid }
.app__cntswap > *{ grid-area:1/1 }
.app__cntswap > *{
  animation-duration:12s; animation-iteration-count:infinite;
  animation-timing-function:steps(1);
  animation-play-state:var(--play,running);
}
.app__cntswap > :nth-child(1){ animation-name:app-cnt1 }
.app__cntswap > :nth-child(2){ animation-name:app-cnt2 }
.app__cntswap > :nth-child(3){ animation-name:app-cnt3 }
@keyframes app-cnt1{ 0%,31%{opacity:1} 32%,100%{opacity:0} }
@keyframes app-cnt2{ 0%,31%{opacity:0} 32%,64%{opacity:1} 65%,100%{opacity:0} }
@keyframes app-cnt3{ 0%,64%{opacity:0} 65%,97%{opacity:1} 98%,100%{opacity:0} }

/* =========================================================
   REGISTRO CUMPLIMENTADO — copiado del producto, no aproximado
   ---------------------------------------------------------
   Medido en app.trysolved.com el 1 de septiembre de 2026, en la ficha de un
   registro cerrado (/checklist/preview/…/section/0). Lo que se copia y de
   dónde sale cada valor:

     · Botón OK  → fondo #16A34A, texto #DCFCE7   (medido)
     · Botón KO  → fondo blanco, borde y texto #DC2626 (medido)
     · Chip verde de estado → borde #BBF7D0, texto #15803D (medido)
     · N/A y textos secundarios → #4B5563 (medido)
     · Hairline de tarjeta → #E5E7EB (medido)
     · Tipografía → Roboto, que ya era la del resto de ds/app.css

   El verde OK del producto resultó ser exactamente --color-status-ok de
   ds/tokens.css. No es casualidad y no se toca.

   La gramática también es la real: cabecera con ID en chip, el par OK%/KO%,
   el estado, y los controles con su sello "Modificado por … el …". Lo que NO
   se copia es el índice de navegación lateral ni la zona de adjuntos: no caben
   a este tamaño y su ausencia no falsea nada.
   ========================================================= */
.app--registro{ background:#fff; display:flex; flex-direction:column }
.app--registro .rg__cab{
  padding:14px 16px 12px;
  border-bottom:1px solid #E5E7EB;
  display:grid; gap:8px;
}
.app--registro .rg__ttl{ display:flex; align-items:center; gap:8px }
.app--registro .rg__id{
  font:500 11px/1 Roboto, sans-serif;
  color:#15803D; background:#DCFCE7;
  padding:5px 8px; border-radius:6px; letter-spacing:.2px;
}
.app--registro .rg__nom{ font:500 14px/1.2 Roboto, sans-serif; color:#1A1B1F }
.app--registro .rg__estado{ display:flex; align-items:center; gap:12px; flex-wrap:wrap }
.app--registro .rg__pct{
  display:inline-flex; align-items:center; gap:6px;
  font:500 11.5px/1 Roboto, sans-serif; color:#4B5563;
}
.app--registro .rg__pct::before{
  content:""; width:7px; height:7px; border-radius:50%; background:var(--rg-dot,#16A34A);
}
.app--registro .rg__pct--ok{ --rg-dot:#16A34A }
.app--registro .rg__pct--ko{ --rg-dot:#DC2626 }
.app--registro .rg__chip{
  font:500 10.5px/1 Roboto, sans-serif; color:#15803D;
  border:1px solid #BBF7D0; border-radius:12px; padding:4px 9px;
}
.app--registro .rg__meta{ font:400 10.5px/1.3 Roboto, sans-serif; color:#4B5563 }

.app--registro .rg__lista{ display:grid; gap:10px; padding:14px 16px 16px }
.app--registro .rg__ctrl{
  border:1px solid #E5E7EB; border-radius:10px;
  padding:11px 12px; display:grid; gap:9px;
  border-left:3px solid #16A34A;
}
.app--registro .rg__ctrl--ko{ border-left-color:#DC2626 }
.app--registro .rg__ctrl--na{ border-left-color:#D1D5DB }
.app--registro .rg__fila{ display:flex; align-items:center; gap:9px }
.app--registro .rg__n{
  width:20px; height:20px; flex:none;
  display:grid; place-items:center; border-radius:6px;
  background:#DCFCE7; color:#15803D;
  font:500 11px/1 Roboto, sans-serif;
}
.app--registro .rg__ctrl--ko .rg__n{ background:#FEE2E2; color:#DC2626 }
.app--registro .rg__ctrl--na .rg__n{ background:#F3F4F6; color:#4B5563 }
.app--registro .rg__ctxt{ font:400 12.5px/1.3 Roboto, sans-serif; color:#1A1B1F }
.app--registro .rg__sello{
  display:inline-flex; align-items:center; gap:6px;
  font:400 10px/1 Roboto, sans-serif; color:#4B5563;
  background:#F3F4F6; border-radius:6px; padding:5px 8px;
  width:fit-content;
}
.app--registro .rg__sello svg{ width:11px; height:11px }
/* El par de botones del producto: KO en blanco con borde rojo, OK macizo. */
.app--registro .rg__bts{ display:flex; gap:8px; justify-content:flex-end }
.app--registro .rg__bt{
  font:500 12px/1 Roboto, sans-serif;
  padding:8px 22px; border-radius:999px; border:1px solid transparent;
}
.app--registro .rg__bt--ko{ color:#DC2626; border-color:#DC2626; background:#fff }
/* El resultado elegido va macizo y el otro apagado, que es como se lee en el
   producto: allí el control estaba en OK y el verde era sólido. Aquí el
   control 1 está en KO, así que el macizo es el rojo. */
.app--registro .rg__bt--ko.rg__bt--on{ background:#DC2626; color:#FEE2E2 }
.app--registro .rg__bt--ok{ color:#DCFCE7; background:#16A34A }
.app--registro .rg__bt--off{ opacity:.32 }
/* Los controles ya resueltos se pliegan a una línea, como en el producto. */
.app--registro .rg__res{ margin-left:auto; font:500 11.5px/1 Roboto, sans-serif }
.app--registro .rg__res--ok{ color:#15803D }
.app--registro .rg__res--na{ color:#4B5563 }

/* =========================================================
   LISTADO DE REGISTROS — copiado del producto
   ---------------------------------------------------------
   Medido en app.trysolved.com/checklist el 1 de septiembre de 2026, pestaña
   Historial. Se copian las tres pestañas con su contador, el par
   Detallada/Compacta, el separador de fecha en píldora centrada entre
   hairlines, y la fila: miniatura, ID en chip, título, estado con punto,
   chip Processing, "Ejecutado el:" y las píldoras de Responsable y Verificador.

   Lo que NO se copia: la paginación y el buscador. A este tamaño son ruido y
   quitarlos no falsea el producto.
   ========================================================= */
.app--reglista{ background:#F7F8FA }
/* LA PANTALLA SE QUEDA EN LO QUE CUENTA: cabecera, el filtro que se pulsa y
   los registros. Fuera la barra de Lista/Calendario y las pestañas
   Pendientes/Historial, y fuera el conmutador Detallada/Compacta (3 sep 2026).
   Es cromo de aplicación: dentro de un plano de 250 px no se lee, y lo que
   sostiene la escena es que la operaria pide dos filtros y salen sus registros,
   no cuántas vistas tiene el listado. El contador sí se queda —es el remate del
   filtro, el que pasa de «1-4 de 4» a «1-2 de 2»—, y por eso .rl__sub sigue. */
.app--reglista .rl__sub{
  display:flex; align-items:center; gap:8px;
  padding:9px 14px; border-bottom:1px solid #E5E7EB; background:#fff;
}

.app--reglista .rl__fecha{
  display:flex; align-items:center; gap:10px; padding:14px 14px 6px;
}
.app--reglista .rl__fecha::before,
.app--reglista .rl__fecha::after{ content:""; flex:1; height:1px; background:#DBE7FF }
.app--reglista .rl__fechat{
  font:500 10.5px/1 Roboto, sans-serif; color:#1E6BFF;
  background:#EAF1FF; border-radius:999px; padding:6px 12px;
}
.app--reglista .rl__filas{ padding:6px 14px 14px }
.app--reglista .rl__fila + .rl__fila{ margin-top:8px }
.app--reglista .rl__fila{
  display:flex; align-items:center; gap:11px;
  background:#fff; border:1px solid #E5E7EB; border-left:3px solid #16A34A;
  border-radius:9px; padding:9px 11px;
}
.app--reglista .rl__fila--inc{ border-left-color:#9CA3AF }
.app--reglista .rl__mini{
  width:38px; height:30px; flex:none; border-radius:5px;
  background:linear-gradient(135deg,#C7CDD4,#8C949D);
}
.app--reglista .rl__id{
  font:500 10.5px/1 Roboto, sans-serif; color:#15803D;
  background:#DCFCE7; border-radius:5px; padding:4px 6px; flex:none;
}
.app--reglista .rl__cuerpo{ display:grid; gap:4px; min-width:0 }
.app--reglista .rl__nom{ font:500 12px/1.2 Roboto, sans-serif; color:#1A1B1F }
.app--reglista .rl__linea{ display:flex; align-items:center; gap:9px; flex-wrap:wrap }
.app--reglista .rl__est{
  display:inline-flex; align-items:center; gap:5px;
  font:500 10.5px/1 Roboto, sans-serif; color:#15803D;
}
.app--reglista .rl__est::before{ content:""; width:6px; height:6px; border-radius:50%; background:#16A34A }
.app--reglista .rl__est--inc{ color:#4B5563 }
.app--reglista .rl__est--inc::before{ background:#9CA3AF }
.app--reglista .rl__proc{
  font:500 10px/1 Roboto, sans-serif; color:#15803D;
  background:#DCFCE7; border-radius:5px; padding:4px 6px;
}
.app--reglista .rl__cuando{ font:400 10.5px/1 Roboto, sans-serif; color:#4B5563 }
.app--reglista .rl__gente{ display:flex; gap:7px; margin-left:auto; flex:none }
.app--reglista .rl__pers{
  display:flex; align-items:center; gap:6px;
  border:1px solid #E5E7EB; border-radius:8px; padding:4px 8px 4px 4px;
}
.app--reglista .rl__av{
  width:19px; height:19px; border-radius:50%;
  display:grid; place-items:center;
  font:500 9.5px/1 Roboto, sans-serif; color:#fff; background:#1E6BFF;
}
.app--reglista .rl__av--ver{ background:#16A34A }
.app--reglista .rl__rol{ display:grid; gap:1px }
.app--reglista .rl__rolt{ font:400 8.5px/1 Roboto, sans-serif; color:#1E6BFF }
.app--reglista .rl__rolt--ver{ color:#15803D }
.app--reglista .rl__roln{ font:400 10px/1 Roboto, sans-serif; color:#1A1B1F }

/* =========================================================
   EL REGISTRO SE RELLENA — animación del bocadillo
   ---------------------------------------------------------
   La tarjeta quieta enseñaba el resultado; lo que cuenta la página es el acto
   de registrar. Ciclo de 8 s, el mismo que el resto de ds/app.css:

     0,0 s   tarjeta en blanco: sin resultados, contadores a cero
     1,2 s   control 1 → se pulsa KO y su franja se pone roja
     2,6 s   control 2 → OK
     3,8 s   control 3 → OK
     5,0 s   control 4 → N/A
     5,2 s   los porcentajes terminan de subir
     8,0 s   vuelve a empezar

   Se anima el CONTENIDO de la pantalla, nunca la tarjeta: es la distinción del
   sistema, la misma que siguen el dashboard y el parte de la home.

   Los tiempos van escritos a mano en cada keyframe y no con `animation-delay`
   sobre uno común: un retardo positivo desplaza el primer ciclo respecto de
   los demás y la primera pasada se ve descuadrada. Un selector de @keyframes
   tampoco admite variables ni calc(), así que cuatro keyframes cortos es la
   forma honesta de escribir esto.

   Los porcentajes los pinta counter() desde --rg-ok/--rg-ko, que es lo que
   anima el keyframe —un entero no se interpola de otra forma en CSS—, con
   @property para registrarlos. Cada uno lleva su valor final en un style
   inline: es lo que se ve si el navegador no registra @property, y es lo que
   queda con prefers-reduced-motion.

   Todo obedece a --play, como el resto del fichero.
   ========================================================= */
@property --rg-ok{ syntax:'<integer>'; initial-value:0; inherits:false }
@property --rg-ko{ syntax:'<integer>'; initial-value:0; inherits:false }

/* El contador, la variable y la animación van en el MISMO elemento. Con
   @property e `inherits:false` un hijo no recibe el valor animado del padre:
   se queda en el initial-value y el número sale a cero. Es el mismo montaje
   que .app__kpiv--count. */
.app--registro .rg__num{
  font-variant-numeric:tabular-nums;   /* si no, la caja baila al pasar de 9 a 10 */
  animation-duration:8s;
  animation-timing-function:linear;
  animation-iteration-count:infinite;
  animation-play-state:var(--play,running);
}
.app--registro .rg__num::before{ content:counter(n) }
.app--registro .rg__num--ok{ counter-reset:n var(--rg-ok); animation-name:rg-ok }
.app--registro .rg__num--ko{ counter-reset:n var(--rg-ko); animation-name:rg-ko }

@keyframes rg-ok{ 0%,14%{ --rg-ok:0 } 65%,100%{ --rg-ok:67 } }
@keyframes rg-ko{ 0%,14%{ --rg-ko:0 } 65%,100%{ --rg-ko:33 } }

/* Cada resultado entra cuando le toca, y la franja de la celda viaja de gris a
   su color en el mismo instante: es lo que hace que se lea como "resuelto" y
   no como "ha aparecido un texto". */
@keyframes rg-res-2{ 0%,30%{ opacity:0; transform:translateY(3px) } 34%,100%{ opacity:1; transform:none } }
@keyframes rg-res-3{ 0%,45%{ opacity:0; transform:translateY(3px) } 49%,100%{ opacity:1; transform:none } }
@keyframes rg-res-4{ 0%,60%{ opacity:0; transform:translateY(3px) } 64%,100%{ opacity:1; transform:none } }
.app--registro .rg__ctrl:nth-of-type(2) .rg__res{ animation:rg-res-2 8s linear infinite }
.app--registro .rg__ctrl:nth-of-type(3) .rg__res{ animation:rg-res-3 8s linear infinite }
.app--registro .rg__ctrl:nth-of-type(4) .rg__res{ animation:rg-res-4 8s linear infinite }
.app--registro .rg__res{ animation-play-state:var(--play,running); animation-iteration-count:var(--iter,infinite); animation-fill-mode:forwards; }

@keyframes rg-franja-ko{ 0%,14%{ border-left-color:#D1D5DB } 18%,100%{ border-left-color:#DC2626 } }
@keyframes rg-franja-2{ 0%,30%{ border-left-color:#D1D5DB } 34%,100%{ border-left-color:#16A34A } }
@keyframes rg-franja-3{ 0%,45%{ border-left-color:#D1D5DB } 49%,100%{ border-left-color:#16A34A } }
.app--registro .rg__ctrl--ko{ animation:rg-franja-ko 8s linear infinite }
.app--registro .rg__ctrl:nth-of-type(2){ animation:rg-franja-2 8s linear infinite }
.app--registro .rg__ctrl:nth-of-type(3){ animation:rg-franja-3 8s linear infinite }
.app--registro .rg__ctrl{ animation-play-state:var(--play,running); animation-iteration-count:var(--iter,infinite); animation-fill-mode:forwards; }

/* El botón KO se pulsa: pasa de contorno a macizo, y el OK se apaga a la vez,
   que es como queda en el producto cuando el otro está elegido. */
@keyframes rg-pulsa{ 0%,14%{ background:#fff; color:#DC2626 } 18%,100%{ background:#DC2626; color:#FEE2E2 } }
@keyframes rg-apaga{ 0%,14%{ opacity:1 } 18%,100%{ opacity:.32 } }
.app--registro .rg__bt--ko.rg__bt--on{ animation:rg-pulsa 8s linear infinite; animation-play-state:var(--play,running); animation-iteration-count:var(--iter,infinite); animation-fill-mode:forwards; }
.app--registro .rg__bt--ok.rg__bt--off{ animation:rg-apaga 8s linear infinite; animation-play-state:var(--play,running); animation-iteration-count:var(--iter,infinite); animation-fill-mode:forwards; }

/* Quieto: cada pieza se queda en el valor final que ya lleva el marcado. La
   tarjeta cuenta lo mismo, sólo que ya resuelta. */
@media (prefers-reduced-motion:reduce){
  .app--registro .rg__num,
  .app--registro .rg__res,
  .app--registro .rg__ctrl,
  .app--registro .rg__bt--ko.rg__bt--on,
  .app--registro .rg__bt--ok.rg__bt--off{ animation:none }
}

/* Mismo estado resuelto, para cuando la capacidad del rotador ya terminó su
   vuelta única (ver la nota junto a .scene[data-terminado] en ds/scene.css). */
.scene[data-terminado] .app--registro .rg__num,
.scene[data-terminado] .app--registro .rg__res,
.scene[data-terminado] .app--registro .rg__ctrl,
.scene[data-terminado] .app--registro .rg__bt--ko.rg__bt--on,
.scene[data-terminado] .app--registro .rg__bt--ok.rg__bt--off{ animation:none }

/* =========================================================
   FILTROS RÁPIDOS — animación del listado de registros
   ---------------------------------------------------------
   La escena de al lado enseña a la operaria dando DOS toques en la tablet, así
   que la pantalla hace exactamente dos clics: uno por toque. Los tiempos no
   están puestos a ojo — se midieron sobre el propio vídeo, comparando
   fotogramas a 20 fps en la mitad del cuadro donde están las manos: los picos
   de movimiento caen en 1,25 s (25 % del ciclo) y 2,25 s (45 %).

   Los chips son los reales del panel "Filtrar registros" de
   app.trysolved.com/checklist, comprobado el 1 de septiembre de 2026:
   Departamento con Producción / Calidad / Cliente, y "Para hoy". Del panel
   se recorta lo que no cabe —tipo, ID, estado, ejecutor, verificador,
   responsable, planta y rango de fechas— pero no se inventa ninguno.

   Ciclo = duración del vídeo (--rl-dur, la pone ds/sections.js), y el propio
   script devuelve las animaciones a cero en cada vuelta del bucle:

     0 %      panel abierto, ningún filtro puesto, cuatro registros
     25 %     clic 1 → "Calidad"
     45 %     clic 2 → "Para hoy"
     50 %     se pulsa "Aplicar filtros"
     54 %     EL PANEL SE CIERRA, que es lo que hace el producto al aplicar
     55 %     la lista se pliega a los dos que quedan y sube a su sitio
     60 %     el contador pasa de "1–4 de 4" a "1–2 de 2"
     100 %    vuelve a empezar, con el vídeo

   POR QUÉ SE CIERRA EL PANEL Y NO SE QUEDA ABIERTO
   Porque si no, la escena enseña cómo se pide, pero no lo que se pide. Con la
   tarjeta a su tamaño de antes daba igual —cabían el panel y las filas—, pero
   dentro del comparador a media página la tarjeta se recorta por abajo y el
   panel se comía justo la parte que importa: los registros. Cerrarlo no es un
   apaño de encuadre, además: es lo que hace la aplicación de verdad cuando
   pulsas «Aplicar filtros».
   ========================================================= */
.app--reglista .rl__panel{
  padding:11px 14px 12px;
  border-bottom:1px solid var(--app-line,#E6EAF2);
  background:#fff;
  display:grid; gap:9px;
}
.app--reglista .rl__ptit{
  display:flex; align-items:center; gap:7px;
  font:500 11.5px/1 Roboto, sans-serif; color:var(--app-text,#1B2230);
}
.app--reglista .rl__ptit svg{ width:13px; height:13px; color:var(--app-blue,#1E6BFF) }
.app--reglista .rl__grupo{ display:grid; gap:6px }
.app--reglista .rl__glab{ font:400 10px/1 Roboto, sans-serif; color:var(--app-mute,#8B95A7) }
.app--reglista .rl__chips{ display:flex; gap:6px; flex-wrap:wrap; align-items:center }
.app--reglista .rl__chip{
  font:400 10.5px/1 Roboto, sans-serif;
  color:var(--app-blue,#1E6BFF);
  border:1px solid var(--app-blue-wash,#C7D9FF);
  border-radius:999px; padding:5px 10px;
  background:#fff;
}
.app--reglista .rl__mas{ font:400 12px/1 Roboto, sans-serif; color:var(--app-mute,#8B95A7) }
.app--reglista .rl__aplica{
  justify-self:end;
  font:500 10.5px/1 Roboto, sans-serif; color:#fff;
  background:var(--app-blue,#1E6BFF); border-radius:7px; padding:7px 11px;
}

/* El panel se cierra al aplicar y deja sitio a la lista. Se animan alto,
   aire y borde a la vez: con sólo el max-height quedaría la franja del padding
   y la línea de abajo flotando sobre las filas. */
@keyframes rl-panel{
  0%,52%{ max-height:220px; opacity:1; padding-top:11px; padding-bottom:12px; border-bottom-width:1px }
  62%,100%{ max-height:0; opacity:0; padding-top:0; padding-bottom:0; border-bottom-width:0 }
}
.app--reglista .rl__panel{
  overflow:hidden;
  animation:rl-panel var(--rl-dur,5.04s) ease-in-out infinite;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
}

/* Los dos clics. Cada chip se enciende en el instante de su toque. */
@keyframes rl-chip-1{ 0%,24%{ background:#fff; color:#1E6BFF; border-color:#C7D9FF } 26%,100%{ background:#1E6BFF; color:#fff; border-color:#1E6BFF } }
@keyframes rl-chip-2{ 0%,44%{ background:#fff; color:#1E6BFF; border-color:#C7D9FF } 46%,100%{ background:#1E6BFF; color:#fff; border-color:#1E6BFF } }
.app--reglista .rl__chip--c1{ animation:rl-chip-1 var(--rl-dur,5.04s) linear infinite; animation-play-state:var(--play,running); animation-iteration-count:var(--iter,infinite); animation-fill-mode:forwards; }
.app--reglista .rl__chip--c2{ animation:rl-chip-2 var(--rl-dur,5.04s) linear infinite; animation-play-state:var(--play,running) }
/* El botón de aplicar acusa el segundo clic. */
@keyframes rl-aplica{ 0%,46%{ transform:none } 50%{ transform:scale(.94) } 55%,100%{ transform:none } }
.app--reglista .rl__aplica{ animation:rl-aplica var(--rl-dur,5.04s) ease-out infinite; animation-play-state:var(--play,running) }

/* Las filas que no cumplen los dos filtros se pliegan: alto y margen a la vez,
   porque el `gap` de una rejilla no se puede animar y quedaría el hueco. */
@keyframes rl-pliega{
  0%,52%{ max-height:80px; opacity:1; margin-top:8px }
  60%,100%{ max-height:0; opacity:0; margin-top:0 }
}
@keyframes rl-pliega-1{
  0%,52%{ max-height:80px; opacity:1 }
  60%,100%{ max-height:0; opacity:0 }
}
.app--reglista .rl__fila--fuera{
  overflow:hidden;
  animation:rl-pliega var(--rl-dur,5.04s) ease-in-out infinite;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
}
.app--reglista .rl__fila--fuera:first-child{ animation-name:rl-pliega-1 }

.app--reglista .rl__cuenta{ position:relative; display:inline-grid }
.app--reglista .rl__cuenta span{ grid-area:1/1; animation:rl-cuenta var(--rl-dur,5.04s) linear infinite; animation-play-state:var(--play,running) }
.app--reglista .rl__cuenta .rl__c2{ animation-name:rl-cuenta-2 }
@keyframes rl-cuenta{ 0%,56%{ opacity:1 } 60%,100%{ opacity:0 } }
@keyframes rl-cuenta-2{ 0%,56%{ opacity:0 } 60%,100%{ opacity:1 } }

@media (prefers-reduced-motion:reduce){
  .app--reglista .rl__chip--c1,
  .app--reglista .rl__chip--c2,
  .app--reglista .rl__aplica,
  .app--reglista .rl__fila--fuera,
  .app--reglista .rl__cuenta span{ animation:none }
  .app--reglista .rl__chip--c1,
  .app--reglista .rl__chip--c2{ background:#1E6BFF; color:#fff; border-color:#1E6BFF }
  .app--reglista .rl__fila--fuera{ display:none }
  .app--reglista .rl__cuenta .rl__c1{ opacity:0 }
  /* Sin animación se enseña el final, que es donde está el argumento: los dos
     registros que quería ver, no el panel con el que los pidió. */
  .app--reglista .rl__panel{ display:none }
}

/* Mismo estado resuelto, para cuando la capacidad del rotador ya terminó su
   vuelta única (ver la nota junto a .scene[data-terminado] en ds/scene.css). */
.scene[data-terminado] .app--reglista .rl__chip--c1,
.scene[data-terminado] .app--reglista .rl__chip--c2,
.scene[data-terminado] .app--reglista .rl__aplica,
.scene[data-terminado] .app--reglista .rl__fila--fuera,
.scene[data-terminado] .app--reglista .rl__cuenta span{ animation:none }
.scene[data-terminado] .app--reglista .rl__chip--c1,
.scene[data-terminado] .app--reglista .rl__chip--c2{ background:#1E6BFF; color:#fff; border-color:#1E6BFF }
.scene[data-terminado] .app--reglista .rl__fila--fuera{ display:none }
.scene[data-terminado] .app--reglista .rl__cuenta .rl__c1{ opacity:0 }
.scene[data-terminado] .app--reglista .rl__panel{ display:none }

/* =========================================================
   FICHA DE ACTIVO — la pantalla del módulo Activos
   Comprobada contra la aplicación (demo.trysolved.com/assets/…, ficha de un
   activo de la base «Maquinaria») el 3 de septiembre de 2026. Lo que se
   reproduce es lo que hay: cabecera con el código y el tipo de activo, cuatro
   contadores —actividad total, incidencias, checklists y acciones—, el panel
   de detalles con los atributos del tipo, el del código QR con sus dos
   botones, y el historial filtrable por módulo. Nada más: la ficha real no
   tiene gráficas, ni contador de horas, ni stock de repuestos, y pintarlos
   aquí sería vender lo que no está.

   OJO CON LOS RÓTULOS: dentro de la pantalla el contador dice «Checklists»,
   que es como se llama en la aplicación, aunque la web lo llame Registros.
   El fragmento no se retraduce para que pegue con el copy de la página.

   El recorte es por la derecha: el panel de acciones abiertas y la mitad del
   QR se salen de la ventana, que es lo que dice «esto continúa».
   ========================================================= */
.app__act{
  display:flex; flex-direction:column; gap:9px;
  min-width:var(--app-act-w,760px);
}

/* Cabecera: el cuadro del icono, el código con el nombre, el tipo de activo
   debajo y el estado a la derecha. VA SIN TARJETA, como en la ficha real: es
   el encabezado de la página, no un panel más. */
.app__acthead{ display:flex; align-items:center; gap:10px; padding:1px 2px }
.app__actico{
  width:32px; height:32px; border-radius:9px; flex:0 0 auto;
  background:var(--app-line); color:var(--app-text-2);
  display:grid; place-items:center;
}
.app__actico svg{ width:17px; height:17px }
.app__actt{ font-size:13px; font-weight:500; color:var(--app-text) }
.app__acts{ font-size:10px; color:var(--app-text-2); margin-top:1px }
/* El estado del alta —activo o retirado—, no la criticidad: ésa va en los
   detalles y va en palabras, no en color. */
.app__actbadge{
  margin-left:auto; flex:0 0 auto;
  display:inline-flex; padding:2px 9px; border-radius:999px;
  background:#E4F6EA; color:#12813B; font-size:10px; font-weight:500;
}

/* Los rótulos de panel de esta pantalla van en versalitas, como en la app. */
.app__panelt--caps{
  text-transform:uppercase; letter-spacing:.06em;
  font-size:9.5px; color:var(--app-text-2);
}

/* Dos columnas, como en la ficha real: a la izquierda los detalles y debajo el
   historial; a la derecha el código QR y las acciones abiertas. La columna
   estrecha es la que se sale por el borde de la ventana, así que lo que se
   recorta es el QR y no el historial —que es lo que la tarjeta demuestra—. */
.app__actcols{ display:grid; grid-template-columns:minmax(0,1.9fr) minmax(0,1fr); gap:9px; align-items:start }
.app__actcol{ display:flex; flex-direction:column; gap:9px; min-width:0 }

/* Atributos del tipo de activo. Son pares, así que van en <dl>: los nombres
   los pone quien configura el tipo, no vienen fijos en el producto. */
.app__kv{ margin:8px 0 0; display:grid; grid-template-columns:repeat(3,1fr); gap:7px 14px }
.app__kv > div{ min-width:0 }
.app__kv dt{
  font-size:9.5px; text-transform:uppercase; letter-spacing:.06em;
  color:var(--app-mute);
}
.app__kv dd{ margin:2px 0 0; font-size:11px; font-weight:500; color:var(--app-text) }

/* Código QR. Se pega en la máquina y lleva a esta misma ficha. */
.app__qr{ display:flex; align-items:center; gap:11px; margin-top:9px }
/* Iba a pasar la cuarta vez: `.app img{width:auto;height:auto}` puntúa
   0-1-1 y le ganaba a `.app__qrimg` (0-1-0) —el SVG del QR no lleva
   `width`/`height` propios, sólo `viewBox`, así que «auto» lo pintaba a su
   tamaño por defecto (233px) en vez de los 56 que pide esta pantalla. No
   se notaba porque la ficha completa se recortaba antes de llegar aquí;
   al dejar que encaje (29 sep 2026) el QR se salía de su propio panel.
   Se ancla con `.app .app__qrimg`, que empata a 0-2-0 y gana por ir
   después. */
.app .app__qrimg{
  width:56px; height:56px; flex:0 0 auto; display:block;
  /* El código se sirve como SVG y se pinta a tamaño entero de módulo: a 56px
     un PNG de 100 se vería con los bordes sucios. */
}
.app__qrtxt{ min-width:0 }
.app__qrcap{ font-size:10px; color:var(--app-text-2) }
/* En columna: la columna del QR mide ~180px en la ficha de esta página
   —el recorte por la derecha que ya se llevaba el panel de al lado—, y
   «Descargar»/«Imprimir» en fila no caben los dos (29 sep 2026, «se
   sale»). En fila caben si el panel es más ancho —no hay otro sitio que
   use este componente hoy—, así que si algún día se reutiliza en una
   caja más ancha, esto vuelve a `flex-direction:row`. */
.app__qrbtns{ display:flex; flex-direction:column; gap:6px; margin-top:7px }
.app__qrbtns .app__add{ justify-content:center }

/* Historial del activo. Las pestañas filtran por módulo y el listado es el
   mismo de siempre; lo que cambia es que aquí la clave es el activo. */
.app__acttabs{ display:flex; gap:5px; margin-left:auto }
.app__acttab{
  padding:3px 9px; border-radius:999px;
  font-size:9.5px; color:var(--app-text-2); background:var(--app-canvas);
  white-space:nowrap;
}
.app__acttab--on{ background:var(--app-blue); color:#fff; font-weight:500 }
.app__panelt--row{ display:flex; align-items:center; gap:9px }
/* La columna de módulo va con la tinta de su módulo, que es la regla del
   sistema: el color dice de dónde sale el dato. */
.app__mod{ font-size:10px; font-weight:500 }
.app__mod--incidencias{ color:var(--app-blue-deep) }
.app__mod--acciones{ color:var(--app-accion) }
.app__mod--registros{ color:var(--app-registro) }


/* Acciones abiertas del activo: la columna estrecha de la ficha. Es un listado
   corto, no una tabla —la tabla ya está en el historial de al lado—. */
.app--activo .app__kpi{ padding:7px 11px }
.app--activo .app__kpiv{ font-size:19px; margin-top:3px }

.app__oalist{ margin-top:9px; display:flex; flex-direction:column; gap:8px }
.app__oa{ font-size:10.5px; color:var(--app-text) }
.app__oad{ font-size:10.5px; color:var(--app-text-2); margin-top:1px }
.app__oam{ display:flex; align-items:center; gap:6px; margin-top:4px; font-size:10px; color:var(--app-mute) }

/* =========================================================
   CARPETA DE DOCUMENTOS — la pantalla del módulo Documentos
   Comprobada contra la aplicación (demo.trysolved.com/documents, vista de
   tabla de una carpeta) el 3 de septiembre de 2026. Se reproduce lo que hay:
   la franja de «Documentos requeridos» con su frase y su aviso ámbar, y la
   tabla con NOMBRE —nombre, quien lo subió y el marcador de pendiente—,
   SUBIDO EL, FECHA DE REVISIÓN, FECHA DE EXPIRACIÓN y ESTADO.

   LO QUE NO SE PINTA, PORQUE NO ESTÁ EN ESTA VISTA: el número de versión (va
   en la vista de tarjetas, no en la tabla), el visor y el botón de descarga de
   la columna ACCIONES —que en el bocadillo caen fuera— y cualquier cosa que
   la ficha real no tenga. Los dos estados son los dos que existen: «Pendiente
   de validación» y «Validado».

   El rótulo de estado va con punto de color y texto del mismo color, no con
   píldora rellena: es como lo pinta la aplicación, y además a este tamaño una
   píldora rellena pesaría más que el nombre del documento.

   Va dentro de un bocadillo de .stage (ds/sections.css), sin marco de
   dispositivo: en el bocadillo el marco sobra.
   ========================================================= */
.app--docs{ padding:0; display:flex; flex-direction:column; min-width:0 }

/* Miga de pan de la carpeta. */
.app__docbc{
  display:flex; align-items:center; gap:6px;
  padding:9px 12px; border-bottom:1px solid var(--app-line);
  font-size:10px; color:var(--app-mute); white-space:nowrap;
}
.app__docbc b{ font-weight:500; color:var(--app-text) }
.app__docbc svg{ width:11px; height:11px; flex:0 0 auto }

/* Franja de «Documentos requeridos». Es la pieza que distingue a una carpeta
   de Solved de una carpeta compartida: sabe qué tiene que haber dentro. */
.app__req{
  display:flex; align-items:center; gap:10px;
  padding:9px 12px; border-bottom:1px solid var(--app-line);
  background:var(--app-canvas);
}
.app__reqt{ font-size:11px; font-weight:500; color:var(--app-text) }
.app__reqs{ font-size:9.5px; color:var(--app-text-2); margin-top:1px }
.app__reqavi{
  margin-left:auto; flex:0 0 auto;
  display:inline-flex; align-items:center; gap:5px;
  font-size:9.5px; font-weight:500; color:#8A6A12;
}
.app__reqavi::before{
  content:""; width:6px; height:6px; border-radius:50%;
  background:var(--app-warn); flex:0 0 auto;
}

/* Tabla de la carpeta. Aquí sí se conforma al ancho del bocadillo: la columna
   que importa —el estado— es la última, y recortarla dejaría la tarjeta sin
   argumento. Lo que se estrecha es el nombre, que ya se entiende truncado. */
.app--docs .app__table{ min-width:0 }
.app--docs .app__table th{ font-size:9px; letter-spacing:.04em; padding:7px 10px }
.app--docs .app__table td{ padding:8px 10px; vertical-align:middle }
.app--docs .app__table tr:last-child td{ border-bottom:0 }

.app__docn{ display:flex; align-items:flex-start; gap:8px; min-width:0 }
/* Icono de tipo de fichero. El del PDF va en su rojo porque es el color del
   propio icono en la aplicación, no una señal del sistema. */
.app__docico{
  width:22px; height:22px; border-radius:5px; flex:0 0 auto;
  display:grid; place-items:center;
  background:#FBE9E9; color:#C33;
  font-size:7px; font-weight:500; letter-spacing:.02em;
}
.app__doct{
  font-size:10.5px; font-weight:500; color:var(--app-text);
  line-height:1.25;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
.app__docby{ display:flex; align-items:center; gap:5px; margin-top:3px; font-size:9.5px; color:var(--app-text-2) }
.app__docby .app__ava{ width:15px; height:15px; font-size:8px }
/* Marcador de pendiente: el mismo aviso ámbar que pinta la aplicación al lado
   del nombre. */
.app__docwarn{
  width:15px; height:15px; border-radius:50%; flex:0 0 auto;
  background:var(--app-warn); color:#fff;
  display:grid; place-items:center;
  font-size:10px; font-weight:500; line-height:1;
}

.app__est{ display:inline-flex; align-items:center; gap:5px; font-size:9.5px; font-weight:500; white-space:nowrap }
.app__est::before{ content:""; width:6px; height:6px; border-radius:50%; flex:0 0 auto }
.app__est--val{ color:#12813B }
.app__est--val::before{ background:var(--app-ok) }
.app__est--pend{ color:var(--app-accion) }
.app__est--pend::before{ background:var(--app-accion) }

/* Una fecha vacía se pinta con una raya, como en la aplicación. */
.app__vacio{ color:var(--app-line-2) }
/* Las fechas van en la tinta del texto, no en azul: `.app__num` nació para las
   fechas de vencimiento de las incidencias, donde el azul es el módulo, y aquí
   la aplicación las pinta en gris. Y NO se pinta de rojo una expiración
   próxima: la ficha real no tiene ese estado —los dos que tiene son «Validado»
   y «Pendiente de validación»— y colorear la fecha sería inventar una señal. */
.app--docs .app__num{ color:var(--app-text-2) }

/* En pantalla estrecha el bocadillo pasa a ocupar el ancho de la escena y la
   tabla no cabe. Lo que se cae son las dos columnas de fecha intermedias, no
   el estado: la regla del fragmento dice que lo que queda fuera del corte
   tiene que ser lo prescindible, y aquí lo que la tarjeta demuestra es que un
   documento está validado o pendiente y cuándo expira. */
@media (max-width:820px){
  .app--docs .app__table th:nth-child(2),
  .app--docs .app__table td:nth-child(2),
  .app--docs .app__table th:nth-child(3),
  .app--docs .app__table td:nth-child(3){ display:none }
}

/* =========================================================
   FICHA SIMPLIFICADA DE ACTIVO — `.app--ficha`
   El resumen de la ficha que flota sobre el plano en la composición de planta
   (`.compo`, ds/sections.css). No sustituye a `.app--activo`, que es la ficha
   entera dentro de la ventana del navegador.

   VA EN VERTICAL, Y NO ES UN CAPRICHO: en la composición el aparato que sale
   en la foto es una tablet en vertical, así que una tarjeta apaisada flotando
   a su lado se lee como otra cosa. Lo que se enseña es lo que ese aparato
   tiene en la mano.

   Y VA SIN LA BARRA DE LA APLICACIÓN. En la composición no hay ventana ni
   dispositivo alrededor: es la ficha sola, como en la creatividad de la
   referencia. La barra azul con el logotipo pertenece a las pantallas que van
   dentro de un marco (`.app--activo` en su ventana), no a ésta.

   ⚠️ AVISO — ESTA PANTALLA VA POR DELANTE DEL PRODUCTO
   Cuatro de sus piezas NO existen todavía en la aplicación (comprobado en
   demo.trysolved.com el 3 de septiembre de 2026): la **foto del activo**, y
   las pestañas de **Documentos**, **Imágenes** y **Mantenimientos**. La ficha
   real tiene icono genérico, contadores, DETALLES, ACTIVIDAD, CÓDIGO QR y
   ACCIONES ABIERTAS, y nada más. Se pintan a petición expresa del cliente
   («puedes inventártelo porque dentro de muy poco estarán»). Mientras no
   estén, esta página no debería publicarse. Pendiente 0 de CLAUDE.md.
   ========================================================= */
.app--ficha{
  display:flex; flex-direction:column;
  padding:11px 11px 4px;
  gap:9px;
  min-width:0;
}

/* Miga de pan mínima, como en la referencia: de dónde vengo y qué activo es. */
.fic__bc{
  display:flex; align-items:center; gap:6px;
  font-size:10px; color:var(--app-text-2); white-space:nowrap;
}
.fic__bc svg{ width:11px; height:11px; flex:0 0 auto }
.fic__bc b{ font-weight:500; color:var(--app-text) }

/* La foto del activo: cuadrada y a la izquierda del nombre, como en la
   referencia. Grande —no un icono de listado— pero dentro de la cabecera, que
   es lo que deja sitio para que el nombre respire a su lado. */
.app .fic__foto{
  width:var(--fic-foto, 86px); height:var(--fic-foto, 86px);
  flex:0 0 auto;
  border-radius:10px; object-fit:cover; display:block;
  box-shadow:inset 0 0 0 1px rgba(11,18,32,.08);
}

/* `min-width:0` en el bloque de texto: sin él el titular empuja y la píldora
   de estado se sale por el borde de la tarjeta, que va con overflow oculto. */
.fic__hd{ display:flex; align-items:center; gap:12px }
.fic__hd > div{ min-width:0 }
.fic__hd .app__actbadge{ flex:0 0 auto }
.fic__t{ font-size:14px; font-weight:500; color:var(--app-text); line-height:1.25 }
.fic__s{ font-size:10px; color:var(--app-text-2); margin-top:3px }
.fic__hd .app__actbadge{ margin-left:auto; margin-top:2px }

/* Pestañas: subrayado, no píldora —las píldoras del sistema son los filtros y
   esto es navegación—. En vertical no caben las cinco, así que la última se
   corta por el borde, que es lo que hace un listado de verdad. */
.fic__tabs{
  display:flex; gap:13px; align-items:center;
  border-bottom:1px solid var(--app-line);
  overflow:hidden;
}
.fic__tab{
  padding:0 0 7px;
  font-size:10.5px; color:var(--app-text-2); white-space:nowrap;
  border-bottom:2px solid transparent; margin-bottom:-1px;
}
.fic__tab--on{ color:var(--app-blue); font-weight:500; border-bottom-color:var(--app-blue) }

/* Con la tarjeta ancha los cuatro contadores caben en una fila sin que la
   cifra baje a tamaño de nota al pie, que era el motivo de partirlos en dos. */
.app--ficha .app__kpis{ grid-template-columns:repeat(4,1fr); gap:8px }
.app--ficha .app__kpi{ padding:7px 10px }
.app--ficha .app__kpiv{ font-size:17px; margin-top:2px }

.fic__lab{
  font-size:9.5px; text-transform:uppercase; letter-spacing:.06em;
  color:var(--app-text-2);
}
.app--ficha .app__table{ min-width:0; margin-top:2px }
.app--ficha .app__table td{ padding:7px 0; border-bottom:1px solid var(--app-line); vertical-align:middle }
.app--ficha .app__table tr:last-child td{ border-bottom:0 }
/* En vertical la fila va en dos alturas: el código y la descripción arriba, el
   módulo y la fecha debajo. En una sola línea no cabe sin encoger el texto por
   debajo de los 10px, que es el suelo del sistema. */
.app--ficha .app__table td:last-child{ text-align:right; white-space:nowrap }


/* =========================================================
   LOS TRES PANELES DE PRODUCTO DE /auditorias/ — 9 de septiembre de 2026
   Van con el rotador de capacidades que sustituye a los tres puntos del montaje
   y a la rejilla de seis celdas. El cuarto panel del rotador no está aquí: es
   `.montaje`, que tiene su propia hoja (ds/plantilla.css).

   Las tres pantallas se comprobaron en `demo.trysolved.com` el 9 de septiembre:
   el editor de plantillas (`/checklist/edit/<id>`), la vista Calendario de
   `/checklist` y la pestaña Historial de `/checklist` con el panel de Informes
   Excel de `/reports`.
   ========================================================= */

/* --- EL CONTROL, SU TIPO Y SU FÓRMULA ---------------------------------------
   La barra de tipos es la del editor. Se enseñan nueve de los diecisiete y el
   resto se CUENTA (`+8`): una barra recortada por el borde diría que hay más de
   los que hay, y una inventada, que hay tipos que no existen. */
.app__ctrl{
  background:#fff; border:1px solid var(--app-line); border-radius:9px;
  padding:10px 12px 11px;
}
.app__ctrlh{ display:flex; align-items:center; gap:9px }
.app__ctrln{ font-size:11.5px; font-weight:500; color:var(--app-text) }
.app__ctrlsec{ font-size:10px; color:var(--app-mute); white-space:nowrap }
.app__tipos{ display:flex; flex-wrap:wrap; gap:5px; margin-top:9px }
.app__tchip{
  display:inline-flex; align-items:center;
  padding:4px 9px; border-radius:999px;
  border:1px solid var(--app-line); background:#fff;
  font-size:9.5px; color:var(--app-text-2); white-space:nowrap;
}
/* «Seleccionado» se dice con el lavado azul, como el ítem activo del menú
   lateral y como el chip de filtro. */
.app__tchip--on{
  background:var(--app-blue-wash); border-color:rgba(30,107,255,.28);
  color:var(--app-blue-deep);
}
.app__tmas{ font-size:9.5px; color:var(--app-mute); align-self:center; padding-left:2px }
.app__tipos--pest{ margin-top:0; margin-bottom:2px }

/* La fórmula se monta pieza a pieza, que es como se monta en el constructor:
   valor, operador, valor. El conector Y entra el último a propósito —en el
   producto sólo aparece cuando la expresión previa ya es una comparación
   completa—, y por eso su fotograma va detrás del de la primera comparación.

   Las siete piezas arrancan escondidas EN EL PROPIO ELEMENTO: `animation-delay`
   no pinta el 0 % mientras espera, así que sin `opacity:0` estarían las siete
   puestas desde el principio. En movimiento reducido hay que encenderlas. */
.app__formu{ display:flex; flex-wrap:wrap; align-items:center; gap:5px; margin-top:9px }
.app__fxv,.app__fxo,.app__fxc,.app__fxy{
  display:inline-flex; align-items:center;
  padding:4px 9px; border-radius:6px;
  font-size:9.5px; white-space:nowrap;
  opacity:0;
  animation:app-fx 14s cubic-bezier(.16,1,.3,1) infinite;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
}
/* Valor, operador y constante se distinguen por color, como en el constructor:
   la entrada local es el dato que teclea el operario y va en el azul del
   producto; el operador es neutro; la constante, el número fijo. */
.app__fxv{ background:var(--app-blue-wash); border:1px solid rgba(30,107,255,.28); color:var(--app-blue-deep) }
.app__fxo{ background:var(--app-canvas);   border:1px solid var(--app-line);      color:var(--app-text-2) }
.app__fxc{ background:#fff;                border:1px solid var(--app-line-2);    color:var(--app-text) }
.app__fxy{ background:var(--app-text);     border:1px solid var(--app-text);      color:#fff; font-weight:500 }
.app__fxv--e1{ animation-delay:0s }
.app__fxo--e2{ animation-delay:.32s }
.app__fxc--e3{ animation-delay:.64s }
.app__fxy--e4{ animation-delay:.96s }
.app__fxv--e5{ animation-delay:1.28s }
.app__fxo--e6{ animation-delay:1.6s }
.app__fxc--e7{ animation-delay:1.92s }
@keyframes app-fx{ 0%,2%{opacity:0; scale:.94} 8%,100%{opacity:1; scale:1} }

/* El panel «Ejemplo»: donde se prueba la fórmula antes de guardar. Entra cuando
   la expresión ya está entera —a los 2 s del ciclo— porque probar una fórmula a
   medias no es nada. */
.app__ej{
  display:flex; align-items:center; gap:8px;
  margin-top:10px; padding-top:9px;
  border-top:1px solid var(--app-line);
  font-size:10px; color:var(--app-text-2);
  opacity:0;
  animation:app-ej 14s cubic-bezier(.16,1,.3,1) infinite;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
}
@keyframes app-ej{ 0%,17%{opacity:0; translate:0 4px} 24%,100%{opacity:1; translate:0 0} }
.app__ejt{ color:var(--app-mute) }
.app__ejv{
  padding:3px 9px; border-radius:6px;
  background:#fff; border:1px solid var(--app-line-2);
  font-size:10px; color:var(--app-text);
}
/* El resultado es un estado, así que va con el color del estado. La fórmula
   devuelve booleano y el producto lo mapea a Verdadero (OK) / Falso (KO): el
   rótulo dice las dos cosas porque en el constructor se leen las dos. */
.app__ejr{
  padding:3px 9px; border-radius:999px;
  font-size:9.5px; font-weight:500;
}
.app__ejr--ko{ background:#FBD5D5; color:#B02020 }
.app__reglap{ margin:5px 0 0; font-size:10px; color:var(--app-text-2); line-height:1.4 }

/* --- EL CALENDARIO DEL MÓDULO -----------------------------------------------
   Dos semanas y no cinco: en el panel del rotador, un mes entero deja las
   píldoras a tres píxeles de alto y el nombre del registro ilegible. Lo que se
   enseña es el cambio de estado —la semana que ya se cerró y la que está
   planificada—, que es lo que cuenta la capacidad. */
.app__calh{ display:flex; align-items:center; gap:8px }
.app__calhoy{
  padding:4px 10px; border-radius:6px;
  background:var(--app-blue); color:#fff;
  font-size:9.5px; font-weight:500;
}
.app__calm{ font-size:12px; font-weight:500; color:var(--app-text) }
.app__caleg{ display:flex; flex-wrap:wrap; gap:12px; margin-top:2px }
.app__caleyenda{ display:inline-flex; align-items:center; gap:6px; font-size:10px; color:var(--app-text-2) }
.app__pt{ width:8px; height:8px; border-radius:50%; flex:0 0 auto }
/* Verde lo cerrado, morado lo planificado. El morado es el de Registros: el
   color codifica módulo, y lo planificado es del módulo. */
.app__pt--ok{ background:var(--app-ok) }
.app__pt--plan{ background:var(--app-registro) }
.app__cal{
  margin-top:2px;
  display:grid; grid-template-columns:repeat(7,minmax(0,1fr));
  background:var(--app-line); gap:1px;
  border:1px solid var(--app-line); border-radius:9px; overflow:hidden;
}
.app__cald{
  background:#fff; padding:7px 8px;
  font-size:9.5px; font-weight:500; color:var(--app-text-2); text-align:center;
}
.app__dia{
  background:#fff; padding:6px 6px 8px; min-height:62px;
  display:flex; flex-direction:column; gap:3px;
}
.app__dia--finde{ background:var(--app-canvas) }
.app__dian{ font-size:10px; color:var(--app-text-2) }
.app__dia--hoy .app__dian{ color:var(--app-blue); font-weight:500 }
.app__pill{
  display:block; padding:3px 6px; border-radius:4px;
  font-size:8.5px; color:#fff;
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
}
.app__pill--ok{ background:var(--app-ok) }
.app__pill--plan{ background:var(--app-registro) }
/* Lo planificado entra después de lo cerrado, y en cascada de izquierda a
   derecha: es el gesto de programar la semana que viene. */
.app__pill--plan{
  opacity:0;
  animation:app-pill 14s cubic-bezier(.16,1,.3,1) infinite;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
}
.app__dia:nth-child(16) .app__pill--plan{ animation-delay:.1s }
.app__dia:nth-child(17) .app__pill--plan{ animation-delay:.3s }
.app__dia:nth-child(18) .app__pill--plan{ animation-delay:.5s }
.app__dia:nth-child(19) .app__pill--plan{ animation-delay:.7s }
.app__dia:nth-child(20) .app__pill--plan{ animation-delay:.9s }
/* Las planificadas ya no entran al principio: entran cuando la tarjeta de
   planificación se retira, que es lo que cuenta la secuencia —se programa y
   entonces aparece en el calendario—. Con el 4 % de antes, el calendario se
   llenaba detrás de la tarjeta y al descubrirlo ya estaba todo puesto. */
@keyframes app-pill{ 0%,33%{opacity:0; translate:0 3px} 41%,100%{opacity:1; translate:0 0} }

/* --- EL HISTÓRICO Y SU DESCARGA ---------------------------------------------
   La lista va agrupada por día, como en la aplicación, y cada fila lleva su
   estado y su hora de ejecución. Los tres estados son los que existen:
   «Cerrado», «Cerrado KO» y «Cerrado Incompleto». */
.app__hgrupo{
  margin-top:4px; padding:5px 0 3px;
  font-size:9.5px; color:var(--app-mute);
}
.app__hgrupo:first-of-type{ margin-top:0 }
.app__hfila{
  display:flex; align-items:center; gap:9px;
  background:#fff; border:1px solid var(--app-line); border-radius:8px;
  padding:8px 11px; margin-bottom:5px;
  font-size:10.5px;
}
.app__hnom{
  font-size:10.5px; color:var(--app-text);
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
}
.app__hfec{ font-size:9.5px; color:var(--app-mute); white-space:nowrap }
.app__hest{
  padding:3px 9px; border-radius:999px;
  font-size:9.5px; font-weight:500; white-space:nowrap;
}
.app__hest--ok{ background:#E4F6EA; color:#12813B }
.app__hest--ko{ background:#FBD5D5; color:#B02020 }
.app__hest--inc{ background:var(--app-canvas); color:var(--app-text-2) }

/* El panel de Informes, ENCIMA de la lista. Es otra pantalla del producto
   —`/reports`— y por eso flota en vez de ir en fila: puestas una debajo de otra
   se leerían como una sola, y son dos sitios distintos de la aplicación. */
/* SE ANCLA A LA IZQUIERDA Y A MEDIA ALTURA, Y NO A LA ESQUINA DE ABAJO A LA
   DERECHA. El encuadre `dev-br` mete la ventana por el 13 % y le da el 104 % de
   ancho: de los 624 px del aparato se ven los 522 primeros, y de su alto, los
   338 primeros. Un panel pegado a la esquina inferior derecha del aparato cae
   entero fuera de la tarjeta —pasó—. Si esta escena cambia de encuadre, hay que
   rehacer esta cuenta. */
.app__informes{
  position:absolute; left:14px; top:50%; z-index:3;
  width:min(64%,300px);
  background:#fff; border:1px solid var(--app-line); border-radius:10px;
  box-shadow:0 18px 40px rgba(50,50,93,.18);
  padding:11px 13px 12px;
  opacity:0; translate:0 10px;
  animation:app-informes 14s cubic-bezier(.16,1,.3,1) infinite;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
}
/* Entra al 40 %, no al 26 %: antes se plantaba encima justo cuando el menú del
   registro estaba abierto, y las dos vías de descarga —el registro en PDF y el
   histórico en Excel— se contaban a la vez. Primero una, después la otra. */
@keyframes app-informes{
  0%,40%  { opacity:0; translate:0 10px }
  47%,100%{ opacity:1; translate:0 0 }
}
.app__ifila{ display:flex; align-items:center; gap:9px; margin-top:9px }
/* El nombre del informe no encoge: con `text-overflow` heredado de la fila del
   histórico, «Registros» salía «Regist…» dentro de su propio panel. */
.app__ifila .app__hnom{ flex:0 0 auto; overflow:visible }
.app__descarga{
  display:inline-flex; align-items:center; gap:6px;
  padding:5px 11px; border-radius:7px;
  background:var(--app-blue); color:#fff;
  font-size:10px; font-weight:500; white-space:nowrap;
}
.app__descarga svg{ width:12px; height:12px; flex:0 0 auto }

/* --- EL REGISTRO SE RELLENA, UN CONTROL DE CADA TIPO ------------------------
   Sustituye a la pantalla del editor que había en este panel: la capacidad
   habla de los tipos de control, y un tipo de control se entiende cuando se
   ve RELLENAR, no cuando se lee su nombre en una barra de pestañas. Cinco
   controles, cinco tipos, uno detrás de otro:

     0,8 s  Fórmula   · se teclea 3,4 y la fórmula lo resuelve en OK
     2,0 s  OK/KO     · se pulsa KO y el par se queda macizo en rojo
     3,2 s  Texto     · se escribe la observación que explica ese KO
     4,4 s  Archivos  · se adjunta la foto del termómetro
     5,6 s  Firma     · se traza la firma y queda el sello de quién y cuándo
     6,2 s  el contador de la sección llega a 5/5

   Todo cabe en los primeros siete segundos del ciclo de 14 s A PROPÓSITO: el
   rotador cambia de capacidad cada 7 s (`data-intervalo`) y un panel oculto
   —`hidden` es `display:none`— no anima, así que cada vez que vuelve arranca
   de cero y de un ciclo de 14 s nunca se ve la segunda mitad. Es la misma
   cuenta que siguen los otros tres paneles; aquí está escrita.

   LA GRAMÁTICA ES LA DEL PRODUCTO, comprobada en demo.trysolved.com el 9 de
   septiembre de 2026 (`/checklist/preview/<id>/section/<n>`): cabecera con el
   nombre de la sección, su posición «1 / 3» y los botones «todo OK» y
   «todo N/A»; y cada control en una tarjeta con su número, su nombre, el valor
   debajo, el chip verde «Completado» y el avatar de quien lo rellenó. El
   control abierto enseña el par de botones y su sello «Modificado por … el …»;
   los resueltos se pliegan a una línea. Los colores del par —OK #16A34A sobre
   #DCFCE7, KO #DC2626 sobre #FEE2E2— son los medidos el 1 de septiembre para
   `.app--registro`, no se vuelven a elegir.

   NO SE ETIQUETA EL TIPO DENTRO DE LA PANTALLA. En la aplicación el tipo se ve
   en el editor, no al rellenar, así que poner un chip «Fórmula» encima de cada
   control sería inventar interfaz. Lo que distingue a los cinco es su forma
   —un número con su resultado, un par de botones, una línea de texto, un
   fichero adjunto, un trazo—, que es exactamente como se distinguen delante de
   la máquina. Quien lo nombra es el titular de la escena.
   --------------------------------------------------------------------------- */
.app__secb{ display:flex; align-items:center; gap:8px; flex-wrap:wrap; max-width:560px }
.app__secn{ font-size:12px; font-weight:500; color:var(--app-text); letter-spacing:.02em }
.app__secp{
  padding:3px 8px; border-radius:999px;
  border:1px solid var(--app-line); background:#fff;
  font-size:9px; color:var(--app-text-2); white-space:nowrap;
}
/* Los dos atajos de la cabecera real. Van apagados: son botones, no estados. */
.app__secbt{
  display:inline-flex; align-items:center; gap:5px;
  padding:4px 9px; border-radius:7px;
  border:1px solid var(--app-line); background:#fff;
  font-size:9.5px; color:var(--app-text-2); white-space:nowrap;
}
.app__secbt svg{ width:11px; height:11px; flex:0 0 auto }
.app__secbt--ok{ color:#12813B; border-color:#BBF7D0; background:#F2FBF5 }
/* El contador de la sección: el mismo montaje de contador que .rg__num —la
   variable, el counter() y la animación en el mismo elemento, que con
   `inherits:false` es la única forma de que el hijo no lo pierda—. */
@property --sec-hechos{ syntax:'<integer>'; initial-value:0; inherits:false }
.app__seccont{
  counter-reset:h var(--sec-hechos);
  font-variant-numeric:tabular-nums;
  padding:3px 9px; border-radius:999px;
  background:#DCFCE7; color:#15803D;
  font-size:9.5px; font-weight:500; white-space:nowrap;
  animation:app-sec-cont 14s linear infinite;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
}
.app__seccont::before{ content:counter(h) " / 5" }
@keyframes app-sec-cont{
  0%,5%{ --sec-hechos:0 } 6%,13%{ --sec-hechos:1 } 14%,22%{ --sec-hechos:2 }
  23%,30%{ --sec-hechos:3 } 31%,39%{ --sec-hechos:4 } 40%,100%{ --sec-hechos:5 }
}

/* EL ANCHO Y EL ALTO NO SON LIBRES: LOS DA EL RECORTE. Con `dev-br` se ven los
   584 px de ancho y los 335 de alto primeros de una pantalla de 695x435, así que
   la lista se limita a 560 —si ocupa los 695, el chip «Completado» y el avatar
   caen fuera por la derecha— y las cinco tarjetas se aprietan hasta caber en esos
   335. Es la regla del fragmento al revés: en vez de dejar fuera lo prescindible,
   aquí no sobra nada —cada fila es un tipo de control—, así que lo que se ajusta
   es la fila. Si esta escena cambia de encuadre, esta cuenta se rehace. */
.app__rlista{ display:grid; gap:5px; margin-top:6px; max-width:560px }
.app__rl{
  background:#fff; border:1px solid var(--app-line); border-radius:9px;
  padding:4px 10px 5px;
  display:grid; grid-template-columns:20px 1fr auto; align-items:start; gap:9px;
}
.app__rln{
  width:20px; height:20px; border-radius:6px;
  display:grid; place-items:center;
  background:var(--app-canvas); color:var(--app-text-2);
  font-size:10.5px; font-weight:500;
  /* La casilla del número se pone verde cuando el control queda resuelto, que
     es lo que hace la aplicación. */
  animation:app-rl-n 14s steps(1,end) infinite;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
}
.app__rlt{ margin:0; font-size:10.5px; line-height:1.25; color:var(--app-text) }
.app__rlv{ margin:3px 0 0; font-size:10.5px; color:var(--app-text-2); min-height:13px }
.app__rlder{ display:flex; align-items:center; gap:7px }
.app__rlest{
  display:inline-flex; align-items:center; gap:5px;
  padding:3px 8px; border-radius:999px;
  border:1px solid #BBF7D0; background:#F2FBF5; color:#15803D;
  font-size:9px; font-weight:500; white-space:nowrap;
}
.app__rlest svg{ width:10px; height:10px }
.app__rlwho{
  width:18px; height:18px; border-radius:50%;
  display:grid; place-items:center;
  background:var(--app-blue); color:#fff;
  font-size:9px; font-weight:500;
}
/* El chip y el avatar entran juntos, en el instante en que el control se
   resuelve: lo que aparece no es un adorno, es el sello de que ya está. */
.app__rlder{
  opacity:0;
  animation:app-rl-sello 14s cubic-bezier(.16,1,.3,1) infinite;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
}

/* Los cinco instantes. Van escritos uno a uno y no con `animation-delay` sobre
   un keyframe común: un retardo positivo descuadra la primera pasada, que es
   justo la que se ve al entrar el panel. */
@keyframes app-rl-sello{ 0%,6%{opacity:0; translate:0 3px} 9%,100%{opacity:1; translate:0 0} }
@keyframes app-rl-n{ 0%,6%{ background:var(--app-canvas); color:var(--app-text-2) } 9%,100%{ background:#DCFCE7; color:#15803D } }
.app__rl:nth-child(2) .app__rlder{ animation-name:app-rl-sello-2 }
.app__rl:nth-child(3) .app__rlder{ animation-name:app-rl-sello-3 }
.app__rl:nth-child(4) .app__rlder{ animation-name:app-rl-sello-4 }
.app__rl:nth-child(5) .app__rlder{ animation-name:app-rl-sello-5 }
.app__rl:nth-child(2) .app__rln{ animation-name:app-rl-n-2 }
.app__rl:nth-child(3) .app__rln{ animation-name:app-rl-n-3 }
.app__rl:nth-child(4) .app__rln{ animation-name:app-rl-n-4 }
.app__rl:nth-child(5) .app__rln{ animation-name:app-rl-n-5 }
@keyframes app-rl-sello-2{ 0%,14%{opacity:0; translate:0 3px} 17%,100%{opacity:1; translate:0 0} }
@keyframes app-rl-sello-3{ 0%,23%{opacity:0; translate:0 3px} 26%,100%{opacity:1; translate:0 0} }
@keyframes app-rl-sello-4{ 0%,31%{opacity:0; translate:0 3px} 34%,100%{opacity:1; translate:0 0} }
@keyframes app-rl-sello-5{ 0%,40%{opacity:0; translate:0 3px} 43%,100%{opacity:1; translate:0 0} }
@keyframes app-rl-n-2{ 0%,14%{ background:var(--app-canvas); color:var(--app-text-2) } 17%,100%{ background:#FEE2E2; color:#DC2626 } }
@keyframes app-rl-n-3{ 0%,23%{ background:var(--app-canvas); color:var(--app-text-2) } 26%,100%{ background:#DCFCE7; color:#15803D } }
@keyframes app-rl-n-4{ 0%,31%{ background:var(--app-canvas); color:var(--app-text-2) } 34%,100%{ background:#DCFCE7; color:#15803D } }
@keyframes app-rl-n-5{ 0%,40%{ background:var(--app-canvas); color:var(--app-text-2) } 43%,100%{ background:#DCFCE7; color:#15803D } }

/* 1 · FÓRMULA. El valor se teclea y detrás llega el resultado, que es el orden
   real: primero el dato, después lo que la fórmula decide con él. El rango es
   el que ya enseñaba este mismo panel —0 a 4 °C—, así que 3,4 da OK. */
.app__rlnum{
  display:flex; align-items:center; gap:7px; margin-top:3px;
  font-size:10.5px; color:var(--app-text); font-variant-numeric:tabular-nums;
}
.app__rlcaja{
  padding:3px 9px; border-radius:6px;
  border:1px solid var(--app-line-2); background:#fff;
  min-width:52px;
}
.app__rlfx{
  padding:3px 9px; border-radius:999px;
  font-size:9px; font-weight:500;
  background:#DCFCE7; color:#15803D;
  opacity:0;
  animation:app-rl-fx 14s cubic-bezier(.16,1,.3,1) infinite;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
}
@keyframes app-rl-fx{ 0%,7%{opacity:0; scale:.94} 10%,100%{opacity:1; scale:1} }
/* La regla va en la misma línea que el valor: en dos, la tarjeta crece un
   renglón y la quinta fila se cae por debajo del recorte. */
.app__rlregla{ margin:0 0 0 2px; font-size:9px; color:var(--app-mute) }

/* 2 · OK/KO. El par del producto: KO en blanco con borde rojo y OK macizo en
   verde hasta que se elige; al pulsar KO, el rojo se hace macizo y el verde se
   apaga, que es como queda el control resuelto. */
.app__rlpar{ display:flex; gap:7px; margin-top:3px }
.app__rlbt{
  font-size:10px; font-weight:500;
  padding:3px 15px; border-radius:999px; border:1px solid transparent;
}
.app__rlbt--ko{
  color:#DC2626; border-color:#DC2626; background:#fff;
  animation:app-rl-ko 14s steps(1,end) infinite;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
}
.app__rlbt--ok{
  color:#DCFCE7; background:#16A34A;
  animation:app-rl-apaga 14s steps(1,end) infinite;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
}
@keyframes app-rl-ko{ 0%,14%{ background:#fff; color:#DC2626 } 17%,100%{ background:#DC2626; color:#FEE2E2 } }
@keyframes app-rl-apaga{ 0%,14%{ opacity:1 } 17%,100%{ opacity:.32 } }

/* 3 · TEXTO. Se escribe delante de quien mira. `steps()` sobre `clip-path`, que
   es el mismo recurso que ya usa la descripción de la incidencia en esta hoja:
   una anchura animada parte las palabras por la mitad. */
.app__rltxt{
  margin-top:3px; padding:4px 9px; border-radius:7px;
  border:1px solid var(--app-line); background:var(--app-canvas);
  font-size:10px; color:var(--app-text); line-height:1.35;
}
.app__rltxt span{
  display:inline-block;
  clip-path:inset(0 100% 0 0);
  animation:app-rl-teclea 14s steps(38,end) infinite;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
}
@keyframes app-rl-teclea{ 0%,23%{clip-path:inset(0 100% 0 0)} 30%,100%{clip-path:inset(0 0 0 0)} }

/* 4 · ARCHIVOS. La zona de soltar de la aplicación, con su frase literal, y el
   fichero que la ocupa cuando ya está subido. */
/* La zona y el fichero comparten celda: apilados en flujo, el fichero —que
   empieza invisible— reservaba 39px de alto vacíos y esta tarjeta medía 95px
   cuando las demás miden 55, con lo que la quinta se salía del recorte. En la
   misma celda, la tarjeta mide lo que mida el más alto de los dos. */
.app__rlarch{ display:grid; margin-top:3px }
.app__rlarch>*{ grid-area:1/1; align-self:start }
.app__rlzona{
  padding:6px; border-radius:8px;
  border:1px dashed var(--app-line-2); background:var(--app-canvas);
  font-size:9.5px; color:var(--app-mute); text-align:center;
  overflow:hidden;
  animation:app-rl-zona 14s cubic-bezier(.16,1,.3,1) infinite;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
}
/* La zona no se apaga: se CIERRA. Con sólo la opacidad deja su hueco de 40px y
   el fichero aparece flotando debajo de un agujero; con el alto, el relleno y
   el borde a cero, la tarjeta se recoge y el fichero ocupa su sitio. Es el
   mismo cierre que usan las incidencias del esquema de conexión. */
@keyframes app-rl-zona{
  0%,31%  { opacity:1; max-height:32px; padding-block:6px; border-width:1px }
  34%,100%{ opacity:0; max-height:0;    padding-block:0;   border-width:0 }
}
.app__rlfile{
  display:inline-flex; justify-self:start; align-items:center; gap:7px;
  padding:6px 10px; border-radius:8px;
  border:1px solid var(--app-line); background:#fff;
  font-size:10px; color:var(--app-text);
  opacity:0;
  animation:app-rl-file 14s cubic-bezier(.16,1,.3,1) infinite;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
}
@keyframes app-rl-file{ 0%,31%{opacity:0; translate:0 4px} 34%,100%{opacity:1; translate:0 0} }
.app__rlfile svg{ width:16px; height:16px; flex:0 0 auto }
.app__rlpeso{ color:var(--app-mute); font-size:9px }

/* 5 · FIRMA. El trazo se dibuja —`stroke-dashoffset`, como la luz de los cables
   del esquema de conexión— y detrás llega el sello de quién firmó y cuándo,
   que es el que la aplicación pone en cada control tocado. */
/* La firma y su sello, en la misma línea: apilados se comían el alto que
   necesita la quinta tarjeta para entrar dentro del recorte. */
.app__rlfirmafila{ display:flex; align-items:center; gap:9px; margin-top:3px }
.app__rlfirma{
  padding:2px 9px; border-radius:8px;
  border:1px solid var(--app-line); background:#fff; width:fit-content;
}
.app__rlfirma svg{ display:block; width:88px; height:22px }
.app__rlfirma path{
  fill:none; stroke:var(--app-text); stroke-width:1.6;
  stroke-linecap:round; stroke-linejoin:round;
  stroke-dasharray:220; stroke-dashoffset:220;
  animation:app-rl-traza 14s cubic-bezier(.4,0,.4,1) infinite;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
}
@keyframes app-rl-traza{ 0%,40%{stroke-dashoffset:220} 47%,100%{stroke-dashoffset:0} }
.app__rlsello{
  display:inline-flex; align-items:center; gap:6px;
  padding:4px 8px; border-radius:6px;
  background:var(--app-canvas); color:var(--app-text-2);
  font-size:9px;
  opacity:0;
  /* Su propio fotograma y no `app-rl-file` con retardo: un retardo positivo
     descuadra la primera pasada, que aquí es la única que se ve. */
  animation:app-rl-sello5 14s cubic-bezier(.16,1,.3,1) infinite;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
}
@keyframes app-rl-sello5{ 0%,47%{opacity:0; translate:0 3px} 50%,100%{opacity:1; translate:0 0} }
/* El pie que sostiene la promesa de la capacidad sin inventar interfaz: los
   cinco que se ven son cinco de los diecisiete tipos que hay. */
/* El pie que decía «cinco de los diecisiete tipos» se retiró: no cabía dentro
   del recorte y la cifra ya está escrita en el texto de la capacidad, que es
   donde se lee sin competir con la pantalla. */

/* --- PROGRAMAR EL REGISTRO: EL BOTÓN, LA FECHA Y EL CALENDARIO ---------------
   La capacidad promete tres cosas —frecuencia, fecha y responsable— y antes se
   enseñaba sólo el resultado: un calendario ya lleno. Aquí se enseña el gesto,
   que es el mismo que el montaje de la plantilla usa en la escena de al lado:
   un cursor que pincha lo que dice el texto.

     0,0 s  el modelo abierto, con su barra de botones
     1,4 s  se pincha «Planificación» y se abre el panel
     2,8 s  se pone la fecha de inicio y entra el responsable
     4,2 s  se guarda, y la tarjeta se retira
     4,6 s  el calendario, con la semana que se acaba de planificar entrando

   Igual que el resto de paneles del rotador: ciclo de 14 s con todo contado en
   los primeros siete, que es lo que se ve antes de que el rotador pase de
   capacidad.

   QUÉ ESTÁ COMPROBADO Y QUÉ NO, que aquí importa más que en las otras tres
   pantallas. Comprobado en demo.trysolved.com el 9 de septiembre de 2026: el
   botón **«Planificación»** con su icono de reloj, en la barra del modelo junto
   a «Publicar», «Crear copia V2» y «Guardar»; el rótulo literal del panel que
   abre —**«Planificación - Programar generación automática de registros»**—; los
   campos de fecha del producto, que son `dd/mm/aaaa` con su icono de calendario
   (los de «Creado el» y «Ejecutado el» de un registro); el rótulo
   **«Responsable»** de la franja de asignaciones; y la vista de calendario con
   su leyenda de cerrados y planificados. NO se ha visto el panel de
   planificación relleno —el modelo con el que se abrió estaba vacío—, así que
   aquí van los dos campos que el producto sí tiene y que la capacidad nombra, y
   ni un rótulo más. Si alguien lo abre sobre un modelo planificado, esta
   pantalla se corrige con lo que salga.
   --------------------------------------------------------------------------- */
.app__plan{
  position:absolute; inset:0; z-index:4;
  background:#fff;
  padding:11px 13px;
  display:flex; flex-direction:column; gap:9px;
  animation:app-plan-sale 14s cubic-bezier(.16,1,.3,1) infinite;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
}
/* La tarjeta se va del todo: si se queda al 0 % de opacidad pero presente, el
   calendario que hay debajo no se puede leer con el ratón encima. */
@keyframes app-plan-sale{ 0%,30%{opacity:1} 34%,100%{opacity:0} }

.app__planhd{ display:flex; align-items:center; gap:8px }
.app__plannom{ font-size:12px; font-weight:500; color:var(--app-text) }
.app__planest{
  padding:3px 8px; border-radius:999px;
  background:#FEF3C7; color:#92600E; font-size:9px; font-weight:500;
}
.app__planbts{ display:flex; align-items:center; gap:7px; margin-top:1px }
.app__planbt{
  display:inline-flex; align-items:center; gap:5px;
  padding:4px 10px; border-radius:7px;
  border:1px solid var(--app-line); background:#fff;
  font-size:9.5px; color:var(--app-text-2); white-space:nowrap;
}
.app__planbt svg{ width:11px; height:11px; flex:0 0 auto }
.app__planbt--pub{ color:#12813B; border-color:#BBF7D0 }
.app__planbt--guarda{ background:var(--app-blue); border-color:var(--app-blue); color:#fff }
/* El de Planificación se enciende al pulsarlo: es el que abre el panel. */
.app__planbt--plan{
  color:var(--app-registro); border-color:rgba(124,58,237,.42);
  animation:app-plan-bt 14s steps(1,end) infinite;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
}
@keyframes app-plan-bt{
  0%,9%  { background:#fff }
  10%,100%{ background:rgba(124,58,237,.10) }
}

.app__planpanel{
  border:1px solid rgba(124,58,237,.28); border-radius:10px;
  background:rgba(124,58,237,.045);
  padding:10px 12px 11px;
  opacity:0;
  animation:app-plan-panel 14s cubic-bezier(.16,1,.3,1) infinite;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
}
@keyframes app-plan-panel{ 0%,10%{opacity:0; translate:0 5px} 14%,100%{opacity:1; translate:0 0} }
.app__plantit{
  margin:0 0 9px; font-size:10.5px; color:var(--app-text);
  display:flex; align-items:center; gap:6px;
}
.app__plantit svg{ width:12px; height:12px; color:var(--app-registro) }
.app__plancampos{ display:flex; flex-wrap:wrap; gap:14px }
.app__plancampo{ display:grid; gap:4px }
.app__planlb{ font-size:9px; color:var(--app-mute) }
/* El campo de fecha del producto: caja con el valor y el icono de calendario a
   la derecha. Arranca en `dd/mm/aaaa`, que es lo que enseña vacío. */
.app__plancaja{
  display:inline-flex; align-items:center; gap:9px;
  padding:5px 9px; border-radius:7px;
  border:1px solid var(--app-line-2); background:#fff;
  font-size:10px; color:var(--app-text); min-width:118px;
}
.app__plancaja svg{ width:12px; height:12px; color:var(--app-mute); margin-left:auto }
.app__planvacio,.app__planvalor{ grid-area:1/1 }
.app__planpar{ display:grid }
.app__planvacio{ color:var(--app-mute); animation:app-plan-vacio 14s steps(1,end) infinite; animation-play-state:var(--play,running); animation-iteration-count:var(--iter,infinite); animation-fill-mode:forwards; }
.app__planvalor{ opacity:0; animation:app-plan-valor 14s steps(1,end) infinite; animation-play-state:var(--play,running); animation-iteration-count:var(--iter,infinite); animation-fill-mode:forwards; }
@keyframes app-plan-vacio{ 0%,21%{opacity:1} 22%,100%{opacity:0} }
@keyframes app-plan-valor{ 0%,21%{opacity:0} 22%,100%{opacity:1} }
.app__planpers{ display:inline-flex; align-items:center; gap:7px }
.app__planav{
  width:18px; height:18px; border-radius:50%;
  display:grid; place-items:center;
  background:var(--app-blue); color:#fff; font-size:9px; font-weight:500;
}
.app__planguarda{
  align-self:flex-start; margin-top:10px;
  display:inline-flex; align-items:center; gap:6px;
  padding:5px 12px; border-radius:7px;
  background:var(--app-registro); color:#fff;
  font-size:10px; font-weight:500;
}

/* EL CURSOR, el mismo de ds/plantilla.css y por el mismo motivo: la punta de la
   flecha está dibujada en (5, 2.5) de un viewBox de 24, así que sin el traslado
   todo lo que señala cae medio dedo desplazado. Las paradas son porcentajes de
   la PANTALLA, medidos con las animaciones pausadas. */
.app__cur{
  position:absolute; z-index:6;
  width:17px; height:17px;
  top:92%; left:22%;
  transform:translate(-3.5px,-1.8px);
  opacity:0;
  filter:drop-shadow(rgba(12,32,64,.35) 0 2px 3px);
  animation:app-cur 14s cubic-bezier(.4,0,.4,1) infinite;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
  pointer-events:none;
}
.app__cur svg{ width:100%; height:100%; display:block }
/* Las paradas son porcentajes de `.app` (695x434), medidas con las animaciones
   pausadas en cada instante: el botón de Planificación, el campo de fecha —a la
   izquierda de la caja, que es donde se escribe— y el botón de guardar. El
   cursor se va antes de que aparezca el calendario: lo que pasa allí no lo hace
   nadie con el ratón, se hace solo, que es justo lo que promete la capacidad. */
@keyframes app-cur{
  0%,2%     { opacity:0; left:22%;   top:95% }
  4%        { opacity:1; left:22%;   top:95% }
  9.5%      { opacity:1; left:19.9%; top:12.3% }  /* llega a «Planificación» */
  12%       { opacity:1; left:19.9%; top:12.3% }  /* y lo pulsa: el panel se abre */
  19%       { opacity:1; left:10%;   top:34.4% }  /* al campo de fecha */
  23%       { opacity:1; left:10%;   top:34.4% }  /* que se rellena */
  29%       { opacity:1; left:12.3%; top:42.9% }  /* a guardar la planificación */
  32%       { opacity:1; left:12.3%; top:42.9% }
  35%,100%  { opacity:0; left:12.3%; top:42.9% }
}

/* --- EL REGISTRO CERRADO SE DESCARGA: EL MENÚ, EL FORMATO Y EL ACUSE ---------
   La capacidad promete «listo para descargar» y el panel enseñaba el resultado:
   una lista y un botón. Ahora se enseña el gesto —lo mismo que se hizo con la
   planificación—: se abre el menú del registro, se elige qué se descarga y el
   fichero baja.

     0,0 s  el histórico
     1,3 s  se pulsa el menú del registro cerrado y se abre
     3,1 s  se elige «Descargar PDF» y el menú se cierra
     3,9 s  el acuse con el nombre del fichero
     5,6 s  el panel de Informes Excel, que es la otra vía —el histórico entero,
            no un registro— y por eso entra después y no a la vez

   LOS CINCO RÓTULOS SON LITERALES DEL PRODUCTO. Salen del `more_vert` de un
   registro cerrado en `demo.trysolved.com/checklist/preview/<id>`, comprobado el
   9 de septiembre de 2026: «Descargar PDF», «Descargar PDF comprimido»,
   «Descargar solo resultados KO», «Seleccionar secciones...» y «Enviar resumen
   por correo». Con eso queda **resuelto el aviso** que llevaba esta capacidad
   desde que se montó: el PDF del registro firmado existe, y además con opciones.
   No se inventa un selector de formato —el producto no tiene uno— porque no hace
   falta: lo que hay es un menú con cinco maneras de bajarlo, que es más de lo que
   prometía la página.
   --------------------------------------------------------------------------- */
/* El botón de tres puntos, en la fila del registro cerrado. */
.app__kebab{
  display:inline-flex; flex-direction:column; gap:2.5px; align-items:center;
  padding:2px 4px; border-radius:5px; flex:0 0 auto;
  animation:app-kebab 14s steps(1,end) infinite;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
}
.app__kebab i{ width:2.5px; height:2.5px; border-radius:50%; background:var(--app-mute) }
/* Se enciende al pulsarlo, como cualquier botón de icono de la aplicación. */
@keyframes app-kebab{
  0%,9%   { background:transparent }
  10%,25% { background:var(--app-blue-wash) }
  26%,100%{ background:transparent }
}

/* EL MENÚ VIVE DENTRO DEL RECORTE. La fila del registro llega hasta el borde
   de la pantalla (695px) pero de esa pantalla sólo se ven los 584 primeros, así
   que un menú anclado a su derecha se queda medio fuera de la tarjeta —pasó—.
   Las filas del histórico se limitan a 560px y el menú se ancla al 52 % de la
   pantalla: con sus 186px acaba en el 79 %, dentro de lo que se ve. */
.app__hgrupo,
.app__hfila{ max-width:560px }
.app__menu{
  position:absolute; z-index:5;
  left:52%; top:34%;
  min-width:186px;
  background:#fff; border:1px solid var(--app-line); border-radius:10px;
  box-shadow:0 18px 40px rgba(50,50,93,.20);
  padding:5px;
  transform-origin:100% 0;
  opacity:0;
  animation:app-menu 14s cubic-bezier(.16,1,.3,1) infinite;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
}
/* Se abre al pulsar los tres puntos y se cierra al elegir. Un menú que se queda
   abierto después de elegir no es un menú: es una lista. */
@keyframes app-menu{
  0%,9%    { opacity:0; scale:.96 }
  11%,24%  { opacity:1; scale:1 }
  26%,100% { opacity:0; scale:.98 }
}
.app__mit{
  display:flex; align-items:center; gap:8px;
  padding:6px 9px; border-radius:7px;
  font-size:10px; color:var(--app-text); white-space:nowrap;
}
.app__mit svg{ width:13px; height:13px; flex:0 0 auto; color:var(--app-text-2) }
/* El elegido se subraya con el lavado azul —el mismo de un ítem activo— justo
   antes de que el menú se cierre: el orden es señalar, elegir, cerrar. */
.app__mit--on{
  animation:app-mit 14s steps(1,end) infinite;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
}
@keyframes app-mit{
  0%,20%  { background:transparent }
  21%,26% { background:var(--app-blue-wash); color:var(--app-blue-deep) }
  27%,100%{ background:transparent }
}

/* El acuse. Va DEBAJO DE DONDE ESTABA EL MENÚ, no abajo a la izquierda como lo
   pondría el navegador: pegado al pie caía fuera del recorte —del alto de la
   pantalla sólo se ven los 335 primeros de 435— y, además, ahí abajo se pisaba
   con el panel de Informes. Donde está se lee como lo que ha salido de ese menú,
   que es lo que cuenta. */
.app__bajado{
  position:absolute; z-index:5;
  left:52%; top:57%;
  display:inline-flex; align-items:center; gap:8px;
  padding:7px 11px; border-radius:9px;
  background:#fff; border:1px solid var(--app-line);
  box-shadow:0 10px 24px rgba(50,50,93,.14);
  font-size:10px; color:var(--app-text); white-space:nowrap;
  opacity:0;
  animation:app-bajado 14s cubic-bezier(.16,1,.3,1) infinite;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
}
.app__bajado svg{ width:13px; height:13px; flex:0 0 auto; color:#12813B }
@keyframes app-bajado{ 0%,27%{opacity:0; translate:0 6px} 30%,100%{opacity:1; translate:0 0} }

/* El cursor de esta pantalla. Mismas reglas que el de la planificación —la
   punta corregida y las paradas en porcentajes de `.app`, medidas—, y se va en
   cuanto el fichero ha bajado: lo que entra después, el panel de Informes, no lo
   abre nadie con el ratón en esta escena. */
@keyframes app-cur-hist{
  0%,2%    { opacity:0; left:60%;   top:95% }
  4%       { opacity:1; left:60%;   top:95% }
  9%       { opacity:1; left:80.1%; top:33.3% }  /* llega a los tres puntos */
  12%      { opacity:1; left:80.1%; top:33.3% }  /* y abre el menú */
  20%      { opacity:1; left:65.4%; top:38.5% }  /* baja a «Descargar PDF» */
  24%      { opacity:1; left:65.4%; top:38.5% }  /* y lo elige */
  28%,100% { opacity:0; left:65.4%; top:38.5% }
}
.app__cur--hist{ animation-name:app-cur-hist }

@media (prefers-reduced-motion:reduce){
  /* Las cuatro pantallas se quedan resueltas: la fórmula entera con su ejemplo,
     la semana planificada puesta, el panel de informes a la vista y el registro
     con sus cinco controles rellenados. Van con `opacity:1` a mano porque
     arrancan escondidas en el propio elemento. */
  .app__fxv,.app__fxo,.app__fxc,.app__fxy,
  .app__ej,.app__pill--plan,.app__informes,
  .app__rlder,.app__rlfx,.app__rlfile,.app__rlsello{
    animation:none !important; opacity:1; scale:1; translate:0 0;
  }
  /* Programar: quieto se queda el final —el calendario ya planificado—, que es
     el estado que cuenta la capacidad. La tarjeta y el cursor se van. */
  .app__plan,.app__cur{ animation:none !important; opacity:0 }
  /* Descargar: quieto se queda el final —el acuse del PDF y el panel de
     Informes—, con el menú cerrado, que es el estado en el que queda la
     pantalla después de bajar el fichero. */
  .app__menu{ animation:none !important; opacity:0 }
  .app__bajado{ animation:none !important; opacity:1; translate:0 0 }
  .app__kebab{ animation:none !important }
  .app__rltxt span{ animation:none !important; clip-path:none }
  .app__rlfirma path{ animation:none !important; stroke-dashoffset:0 }
  .app__rlzona{ animation:none !important; display:none }
  .app__seccont{ animation:none !important; --sec-hechos:5 }
  /* Quieto, el par ya está elegido y la casilla del control ya tiene su color. */
  .app__rlbt--ko{ animation:none !important; background:#DC2626; color:#FEE2E2 }
  .app__rlbt--ok{ animation:none !important; opacity:.32 }
  .app__rln{ animation:none !important; background:#DCFCE7; color:#15803D }
  .app__rl:nth-child(2) .app__rln{ background:#FEE2E2; color:#DC2626 }
}

/* Mismo estado resuelto, para cuando la capacidad del rotador ya terminó su
   vuelta única (ver la nota junto a .scene[data-terminado] en ds/scene.css). */
.scene[data-terminado] .app__fxv,
.scene[data-terminado] .app__fxo,
.scene[data-terminado] .app__fxc,
.scene[data-terminado] .app__fxy,
.scene[data-terminado] .app__ej,
.scene[data-terminado] .app__pill--plan,
.scene[data-terminado] .app__informes,
.scene[data-terminado] .app__rlder,
.scene[data-terminado] .app__rlfx,
.scene[data-terminado] .app__rlfile,
.scene[data-terminado] .app__rlsello{ animation:none !important; opacity:1; scale:1; translate:0 0; }
.scene[data-terminado] .app__plan,
.scene[data-terminado] .app__cur{ animation:none !important; opacity:0 }
.scene[data-terminado] .app__menu{ animation:none !important; opacity:0 }
.scene[data-terminado] .app__bajado{ animation:none !important; opacity:1; translate:0 0 }
.scene[data-terminado] .app__kebab{ animation:none !important }
.scene[data-terminado] .app__rltxt span{ animation:none !important; clip-path:none }
.scene[data-terminado] .app__rlfirma path{ animation:none !important; stroke-dashoffset:0 }
.scene[data-terminado] .app__rlzona{ animation:none !important; display:none }
.scene[data-terminado] .app__seccont{ animation:none !important; --sec-hechos:5 }
.scene[data-terminado] .app__rlbt--ko{ animation:none !important; background:#DC2626; color:#FEE2E2 }
.scene[data-terminado] .app__rlbt--ok{ animation:none !important; opacity:.32 }
.scene[data-terminado] .app__rln{ animation:none !important; background:#DCFCE7; color:#15803D }
.scene[data-terminado] .app__rl:nth-child(2) .app__rln{ background:#FEE2E2; color:#DC2626 }


/* =========================================================
   LA CONVERSACIÓN DE UNA INCIDENCIA — 9 de septiembre de 2026
   El cuarto cubo de «Detecta» en /incidencias/: la ficha a la izquierda y el
   chat abierto a la derecha, que es como se ve en la aplicación.

   Comprobado en `demo.trysolved.com` el 9 de septiembre: el cajón se abre desde
   el icono de la cabecera de la incidencia, lleva «Chat de la incidencia» con el
   código debajo, una campana para seguir el hilo y, al fondo, el campo con
   marcador «Mensaje» y sus dos botones —adjuntar y enviar—.

   ⚠️ LA BURBUJA NO ESTÁ COMPROBADA: la incidencia que se abrió tenía el chat
   vacío, así que autor, hora, texto y adjunto van con el patrón del resto de la
   aplicación y no contra una captura. Hay que verlo con mensajes dentro antes de
   publicar.
   ========================================================= */

/* Los iconos de la cabecera de la incidencia, reutilizados aquí para la campana
   del cajón y para adjuntar y enviar. */
.app__inchic{
  width:22px; height:22px; border-radius:6px; flex:0 0 auto;
  display:grid; place-items:center;
  color:var(--app-mute);
}
.app__inchic svg{ width:13px; height:13px; display:block }
.app__inchic--on{ background:var(--app-blue); color:#fff }
.app__inchic--enviar{ color:var(--app-blue) }

/* Dos columnas: la ficha y el cajón. El cajón manda —es lo que la tarjeta
   enseña—, así que se lleva la mitad larga; y va con su propio canto y su
   sombra, porque en la aplicación está por encima de la ficha, no al lado. */
/* EL CAJÓN VA SOLO, sin ficha al lado y sin marco de aparato: la escena mide
   cuatro columnas y ahí una ventana entera no cabe. Es el mismo tratamiento que
   el panel de la acción nueva dos cuadros más arriba —panel suelto, su propia
   sombra, `--suelto` en la escena— y por eso comparte medidas con él. */
.app__conv{
  width:100%;
  display:flex; flex-direction:column; gap:7px;
  background:#fff; border:1px solid var(--app-line); border-radius:11px;
  box-shadow:0 16px 32px rgba(50,50,93,.12);
  padding:11px 12px 12px;
  font-family:Roboto,'Helvetica Neue',Arial,sans-serif;
  color:var(--app-text);
  font-variant-numeric:tabular-nums;
  text-align:left;
}
.app__convh{ display:flex; align-items:flex-start; gap:8px; padding-bottom:8px; border-bottom:1px solid var(--app-line) }
.app__convt{ font-size:11.5px; font-weight:500; color:var(--app-text) }
.app__convs{ font-size:9.5px; color:var(--app-mute); margin-top:1px }

/* Los mensajes llegan de uno en uno, que es lo que hace que se lea como una
   conversación y no como un acta. Van escondidos en el propio elemento por lo
   de siempre: `animation-delay` no pinta el 0 % mientras espera. */
.app__msg{
  display:flex; align-items:flex-start; gap:8px;
  opacity:0;
  animation:app-msg 13s cubic-bezier(.16,1,.3,1) infinite;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
}
.app__msg--m2{ animation-delay:.9s }
.app__msg--m3{ animation-delay:1.8s }
@keyframes app-msg{ 0%,3%{opacity:0; translate:0 6px} 11%,100%{opacity:1; translate:0 0} }

/* — LO MÍO A LA DERECHA, LO DEL RESTO A LA IZQUIERDA —
   Es la gramática de cualquier conversación, y aquí además hace un trabajo: sin
   ella los cuatro mensajes se leen como un listado de notas y no se distingue
   quién habla desde fuera y quién desde la propia pantalla. El mío va **sin
   foto** —la aplicación pinta la inicial del usuario, y en una columna de cuatro
   no cabe una cara más— y con el pico del bocadillo en el lado que le toca. */
.app__msg--mio{ flex-direction:row-reverse }
.app__msg--mio .app__msgc{ align-items:flex-end }
.app__msg--mio .app__msgh{ flex-direction:row-reverse }
.app__msg--mio .app__msgb{
  background:var(--app-blue); color:#fff;
  border-top-left-radius:8px; border-top-right-radius:3px;
}
/* Y entra cuando se pulsa enviar, no antes: es el mensaje que se acaba de
   escribir abajo. Sin retardo, con sus propios fotogramas. */
.app__msg--m4{
  animation-name:app-msg-mio; animation-delay:0s;
}
@keyframes app-msg-mio{
  0%,53%   { opacity:0; translate:0 8px; scale:.96 }
  60%,100% { opacity:1; translate:0 0; scale:1 }
}
.app__conv .app__ava--foto{
  width:20px; height:20px; object-fit:cover; margin-top:1px;
  background:var(--app-canvas);
  box-shadow:inset 0 0 0 1px rgba(11,18,32,.08);
}
.app__msgc{ min-width:0; display:flex; flex-direction:column; gap:3px }
.app__msgh{ display:flex; align-items:baseline; gap:7px }
.app__msgh b{ font-size:10px; font-weight:500; color:var(--app-text) }
.app__msgh i{ font-size:9px; font-style:normal; color:var(--app-mute) }
.app__msgb{
  padding:7px 9px; border-radius:8px; border-top-left-radius:3px;
  background:var(--app-canvas);
  font-size:10px; color:var(--app-text); line-height:1.4;
}
/* El adjunto es lo que convierte «alguien lo contó» en evidencia, así que se ve
   como lo que es —un fichero— y no como una palabra subrayada. */
.app__msgadj{
  display:inline-flex; align-items:center; gap:5px; align-self:flex-start;
  padding:4px 8px; border-radius:6px;
  border:1px solid var(--app-line); background:#fff;
  font-size:9px; color:var(--app-blue-deep);
}
.app__msgadj svg{ width:11px; height:11px; flex:0 0 auto }

/* El campo de escribir, pegado al fondo del cajón. El marcador es el literal de
   la aplicación, «Mensaje»; el texto se teclea encima con el mismo relevo seco
   que el campo de responsable de la escena de al lado. */
.app__comp{
  margin-top:auto; padding-top:9px;
  border-top:1px solid var(--app-line);
  display:flex; align-items:center; gap:7px;
}
.app__compi{
  flex:1; min-width:0;
  display:grid;
  padding:6px 9px; border:1px solid var(--app-line); border-radius:7px;
  font-size:10px; min-height:14px; overflow:hidden; white-space:nowrap;
  animation:app-compfoco 13s ease-out infinite;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
}
/* El anillo de foco dice que se está escribiendo AHÍ, y se apaga al enviar. */
@keyframes app-compfoco{
  0%,26%   { border-color:var(--app-line); box-shadow:0 0 0 0 rgba(30,107,255,0) }
  29%,51%  { border-color:rgba(30,107,255,.45); box-shadow:0 0 0 2px var(--app-blue-wash) }
  55%,100% { border-color:var(--app-line); box-shadow:0 0 0 0 rgba(30,107,255,0) }
}
.app__compph,.app__compval{
  grid-area:1/1; min-width:0; overflow:hidden;
  animation-duration:13s; animation-iteration-count:infinite;
  animation-timing-function:ease-out;
  animation-play-state:var(--play,running);
}
.app__compph{ color:var(--app-mute); animation-name:app-compph }
.app__compval{ color:var(--app-text); opacity:0; animation-name:app-compval }
/* Se escribe y SE ENVÍA: el texto entra en el campo, el campo se vacía al pulsar
   y lo que aparece arriba es el mensaje. Los tres relojes van pegados —tecleo
   hasta el 46 %, envío en el 52 %, burbuja en el 53 %— y si se toca uno hay que
   mirar los otros dos, o el mensaje sale antes de mandarse. */
@keyframes app-compph{  0%,26%{opacity:1} 28%,52%{opacity:0} 55%,100%{opacity:1} }
@keyframes app-compval{ 0%,28%{opacity:0} 30%,51%{opacity:1} 53%,100%{opacity:0} }
.app__compval span{
  display:inline-block;
  animation:app-comptec 13s steps(28) infinite;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
}
@keyframes app-comptec{ 0%,30%{clip-path:inset(0 100% 0 0)} 46%,100%{clip-path:inset(0 0 0 0)} }
/* Y el botón de enviar se enciende cuando ya hay algo que enviar. */
.app__inchic--enviar{
  animation:app-enviar 13s ease-out infinite;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
}
/* El botón se enciende cuando ya hay algo que enviar, se hunde al pulsarlo y se
   apaga con el campo. El saltito va ANTES de que el mensaje aparezca arriba, que
   es el orden en que ocurre: primero el dedo, después el envío. */
@keyframes app-enviar{
  0%,31%   { background:transparent; color:var(--app-mute); scale:1 }
  34%,50%  { background:var(--app-blue); color:#fff; scale:1 }
  52%      { background:var(--app-blue); color:#fff; scale:.86 }
  55%,100% { background:transparent; color:var(--app-mute); scale:1 }
}

@media (prefers-reduced-motion:reduce){
  /* La conversación se queda ENVIADA: los cuatro mensajes puestos —el mío a la
     derecha— y el campo otra vez en su marcador, que es el estado en el que
     queda un chat después de mandar. Con el texto en el campo y el mensaje
     arriba a la vez, parecería que se ha escrito dos veces. */
  .app__msg,.app__compph,.app__inchic--enviar,.app__compi{
    animation:none !important; opacity:1; scale:1; translate:0 0;
  }
  .app__compval,.app__compval span{ animation:none !important; opacity:0 }
}

/* Mismo estado resuelto, para cuando la capacidad del rotador ya terminó su
   vuelta única (ver la nota junto a .scene[data-terminado] en ds/scene.css). */
.scene[data-terminado] .app__msg,
.scene[data-terminado] .app__compph,
.scene[data-terminado] .app__inchic--enviar,
.scene[data-terminado] .app__compi{ animation:none !important; opacity:1; scale:1; translate:0 0; }
.scene[data-terminado] .app__compval,
.scene[data-terminado] .app__compval span{ animation:none !important; opacity:0 }

/* ---------------------------------------------------------
   VISTAS DE ACCIONES — /no-conformidades/, «Revisa las tareas en la vista de
   calendario o Kanban». Un solo reloj de 16 s (data-dur del rotador):
     0–44 %    calendario a la vista
     44–50 %   el conmutador pasa a Kanban y las vistas se cruzan
     58–72 %   una tarjeta se levanta en Abierto y baja a En Revisión
     72–94 %   el tablero queda con la tarjeta ya en su sitio
     94–100 %  vuelta al calendario (el reinicio queda oculto tras el cruce)
   Las alturas de tarjeta son fijas (--kh) para que el vuelo aterrice justo
   en el tercer hueco de En Revisión; si cambia el texto o el tamaño de
   letra, hay que revisar --kh y --kh-cab.
   --------------------------------------------------------- */
.app--vistas{ --kh:60px; --kh-cab:30px }
.app__vistas{ position:relative; flex:1; min-height:0 }
.app__vista{
  position:absolute; inset:0;
  display:flex; flex-direction:column; gap:8px;
  animation-duration:16s; animation-timing-function:ease-in-out; animation-iteration-count:infinite;
  animation-play-state:var(--play,running);
}
.app__vista--cal{ animation-name:app-vista-cal }
.app__vista--kan{ animation-name:app-vista-kan; opacity:0 }
@keyframes app-vista-cal{ 0%,47%{opacity:1} 53%,94%{opacity:0} 100%{opacity:1} }
@keyframes app-vista-kan{ 0%,47%{opacity:0} 53%,94%{opacity:1} 100%{opacity:0} }

.app__segb--vcal,.app__segb--vkan{
  animation-duration:16s; animation-iteration-count:infinite; animation-timing-function:step-end;
  animation-play-state:var(--play,running);
}
.app__segb--vcal{ animation-name:app-seg-cal; background:var(--app-blue); color:#fff }
.app__segb--vkan{ animation-name:app-seg-kan }
@keyframes app-seg-cal{ 0%,46%{background:var(--app-blue); color:#fff} 46.01%,99.99%{background:transparent; color:var(--app-text-2)} 100%{background:var(--app-blue); color:#fff} }
@keyframes app-seg-kan{ 0%,46%{background:transparent; color:var(--app-text-2)} 46.01%,99.99%{background:var(--app-blue); color:#fff} 100%{background:transparent; color:var(--app-text-2)} }

/* Tareas del calendario: el color es el de la columna del Kanban en la que
   están, para que las dos vistas se lean como la misma información. */
.app__vista--cal .app__cal{ margin-top:0 }
.app__vista--cal .app__dia{ min-height:0 }
.app__pill--abi{ background:#74C0FC; color:#0B3A5C }
.app__pill--rev{ background:#FFD43B; color:#5C4700 }
.app__pill--cer{ background:#63E6BE; color:#0B4A38 }

/* Kanban con movimiento */
.app__kan--mov{ position:relative; align-content:start }
.app__kan--mov .app__kancard{ height:var(--kh); box-sizing:border-box; overflow:hidden; opacity:1; animation:none }
.app__kan--mov .app__kantxt{ display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden }
.app__kan--mov .app__kanbody{ flex:none }
/* La tarjeta que sale de Abierto: se queda como hueco hasta que la levantan. */
.app__kancard--sale{ animation:app-kan-sale 16s step-end infinite !important; animation-play-state:var(--play,running) !important }
@keyframes app-kan-sale{ 0%,59%{opacity:1} 59.01%,99.99%{opacity:0} 100%{opacity:1} }
/* La que vuela: misma tarjeta, con sombra y ligeramente levantada. Sale del
   segundo hueco de Abierto y aterriza en el tercero de En Revisión. */
.app__kan--mov .app__kancard--vuela{
  position:absolute; z-index:2;
  left:1px; top:calc(var(--kh-cab) + var(--kh));
  width:calc((100% - 18px)/3 - 2px);
  background:#fff; border:1px solid var(--app-line); border-radius:6px;
  opacity:0; pointer-events:none;
  animation:app-kan-vuela 16s cubic-bezier(.5,0,.2,1) infinite !important;
  animation-play-state:var(--play,running) !important;
}
@keyframes app-kan-vuela{
  0%,58.9%{ opacity:0; translate:0 0; scale:1; box-shadow:none; border-radius:0; border-color:transparent }
  59%     { opacity:1; translate:0 0; scale:1; box-shadow:none; border-radius:0; border-color:transparent }
  62%     { opacity:1; translate:0 -3px; scale:1.04; box-shadow:0 10px 22px rgba(50,50,93,.22); border-radius:6px; border-color:var(--app-line) }
  70%     { opacity:1; translate:calc(100% + 11px) var(--kh); scale:1.04; box-shadow:0 10px 22px rgba(50,50,93,.22); border-radius:6px; border-color:var(--app-line) }
  73%,93% { opacity:1; translate:calc(100% + 11px) var(--kh); scale:1; box-shadow:none; border-radius:0; border-color:transparent }
  99.99%  { opacity:1; translate:calc(100% + 11px) var(--kh); scale:1; box-shadow:none; border-radius:0; border-color:transparent }
  100%    { opacity:0; translate:0 0 }
}
@media (prefers-reduced-motion: reduce){
  .app__vista--cal,.app__segb--vcal,.app__segb--vkan,.app__kancard--sale,.app__kancard--vuela{ animation:none !important }
  .app__vista--cal{ opacity:1 } .app__vista--kan{ opacity:0 }
}

/* Mismo estado resuelto, para cuando la capacidad del rotador ya terminó su
   vuelta única (ver la nota junto a .scene[data-terminado] en ds/scene.css). */
.scene[data-terminado] .app__vista--cal,
.scene[data-terminado] .app__segb--vcal,
.scene[data-terminado] .app__segb--vkan,
.scene[data-terminado] .app__kancard--sale,
.scene[data-terminado] .app__kancard--vuela{ animation:none !important }
.scene[data-terminado] .app__vista--cal{ opacity:1 }
.scene[data-terminado] .app__vista--kan{ opacity:0 }
/* Con el encuadre «b» las columnas son estrechas: sin el icono cabe la fecha
   entera y la descripción se queda en dos líneas. */
.app__kan--mov .app__kanico{ display:none }
.app__kan--mov .app__kancard{ padding:8px 10px }
.app__kan--mov .app__kanbody{ min-height:calc(3*var(--kh)) }
.app__kan--mov .app__kandate{ font-size:8px; letter-spacing:-.2px }

/* Calendario del mes entero (lun–dom, 5 filas): número y tarea en la misma
   línea para que las cinco filas quepan en el panel. */
.app__vista--cal .app__dia{ flex-direction:row; align-items:center; gap:4px; padding:4px 4px; min-height:0 }
.app__vista--cal .app__dian{ flex:none; min-width:9px; font-size:9px }
.app__vista--cal .app__pill{ flex:1; min-width:0; padding:2px 4px; font-size:7.5px }
.app__vista--cal .app__dia--otro .app__dian{ color:var(--app-mute); opacity:.6 }
.app__vista--cal .app__cald{ padding:4px 6px; font-size:9px }
.app__vista--cal{ gap:6px }

/* El cursor del ordenador, uno solo para todo el reloj de 16 s, con las
   coordenadas en px relativas a .app__vistas: pasa por una tarea del
   calendario, va al botón «Kanban» y pulsa (44–46 %), agarra la tarjeta
   (59 %) y la arrastra a En Revisión (62–70 %), y se retira. Si cambian
   el encuadre o los tamaños hay que volver a medir estos puntos, y --kh
   ha de coincidir con el de .app--vistas. */
.app__curv{
  position:absolute; left:0; top:0; z-index:6; width:16px; height:16px;
  margin:-2px 0 0 -3px;
  opacity:0; pointer-events:none;
  filter:drop-shadow(rgba(12,32,64,.35) 0 2px 3px);
  animation:app-curv 16s cubic-bezier(.45,0,.25,1) infinite;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
}
.app__curv svg{ width:100%; height:100%; display:block }
/* left/top y no translate: el panel cambia de ancho con la ventana, y los
   puntos que dependen del ancho van en % o en calc(); los que dependen de
   alturas fijas (cabecera de columna, tarjetas de --kh) van en px. */
@keyframes app-curv{
  0%      { opacity:0; left:96%; top:96%; scale:1 }
  6%      { opacity:1; left:96%; top:96%; scale:1 }
  16%,22% { opacity:1; left:77%; top:78%; scale:1 }
  43%     { opacity:1; left:154px; top:-27px; scale:1 }
  44.5%   { opacity:1; left:154px; top:-27px; scale:.8 }
  46%     { opacity:1; left:154px; top:-27px; scale:1 }
  57%     { opacity:1; left:52px; top:calc(var(--kh-cab) + var(--kh) + 30px); scale:1 }
  59%     { opacity:1; left:52px; top:calc(var(--kh-cab) + var(--kh) + 30px); scale:.8 }
  62%     { opacity:1; left:52px; top:calc(var(--kh-cab) + var(--kh) + 27px); scale:.8 }
  70%     { opacity:1; left:calc((100% - 18px)/3 + 61px); top:calc(var(--kh-cab) + 2*var(--kh) + 30px); scale:.8 }
  72%     { opacity:1; left:calc((100% - 18px)/3 + 61px); top:calc(var(--kh-cab) + 2*var(--kh) + 30px); scale:1 }
  84%     { opacity:1; left:96%; top:90%; scale:1 }
  90%,100%{ opacity:0; left:96%; top:96%; scale:1 }
}
@media (prefers-reduced-motion: reduce){ .app__curv{ animation:none !important; opacity:0 } }

/* Mismo estado resuelto, para cuando la capacidad del rotador ya terminó su
   vuelta única (ver la nota junto a .scene[data-terminado] en ds/scene.css). */
.scene[data-terminado] .app__curv{ animation:none !important; opacity:0 }

/* El calendario llena el alto del cuadro: la rejilla se estira y las cinco
   filas se reparten el hueco que deja la cabecera. */
.app__vista--cal .app__cal{ flex:1; min-height:0; grid-template-rows:auto repeat(5,minmax(0,1fr)) }
.app__vista--cal .app__dia{ align-items:flex-start; padding-top:6px }
.app__vista--cal .app__pill{ padding:3px 4px }

/* --- EL CALENDARIO DE ACCIONES, COMO LA APP -----------------------------------
   Réplica de la vista Mes de /actions (libs/ui/calendar): barra blanca con
   ‹ Hoy › y el mes a la izquierda y Mes/Semana/Día/Agenda a la derecha; debajo
   el total, un chip por estado (punto + nombre + cuenta) y el selector de
   fecha; y un cuadro blanco con cabecera de días en negrita, celdas con el
   número arriba a la izquierda, una píldora azul con la cuenta del día arriba
   a la derecha y las tareas como bloques de color de estado con el código en
   negrita y el título debajo. Colores de Tailwind, como en la app: azul-400
   abierto, ámbar-500 en revisión, esmeralda-500 cerrado. Los días de otro mes
   van en gris-50 con el número en gris-400 y el de hoy en azul y negrita. */
.app__vista--cal{ gap:0; background:#F9FAFB; border:1px solid #E5E7EB; border-radius:8px; overflow:hidden }
.cal2{ display:flex; flex-direction:column; height:100%; min-height:0; font-family:Roboto,'Helvetica Neue',Arial,sans-serif }
.cal2__bar{ display:flex; align-items:center; justify-content:space-between; gap:6px; background:#fff; border-bottom:1px solid #E5E7EB; padding:6px 9px 5px }
.cal2__nav{ display:flex; align-items:center; gap:4px }
.cal2__chev{ width:16px; height:16px; color:#374151; display:grid; place-items:center }
.cal2__chev svg{ width:12px; height:12px }
.cal2__hoy{ padding:3px 8px; border-radius:6px; background:var(--app-blue); color:#fff; font-size:9px; font-weight:500 }
.cal2__mes{ margin-left:4px; font-size:11px; font-weight:600; color:#1F2937 }
.cal2__modos{ display:flex; border:1px solid #D1D5DB; border-radius:6px; overflow:hidden; font-size:8.5px; font-weight:500 }
.cal2__modos span{ padding:3px 7px; background:#fff; color:#374151 }
.cal2__modos .on{ background:var(--app-blue); color:#fff }
.cal2__stats{ display:flex; align-items:center; gap:7px; background:#fff; padding:0 9px 6px; font-size:8.5px; color:#4B5563; border-bottom:1px solid #E5E7EB }
.cal2__tot b,.cal2__chip b{ font-weight:700; color:#1F2937 }
.cal2__chip{ display:inline-flex; align-items:center; gap:4px; padding:2px 5px; border-radius:6px }
.cal2__dot{ width:7px; height:7px; border-radius:50% }
.cal2__dot--abi{ background:#3B82F6 } .cal2__dot--rev{ background:#F59E0B } .cal2__dot--cer{ background:#10B981 }
.cal2__sel{ margin-left:auto; padding:2px 6px; border:1px solid #D1D5DB; border-radius:6px; background:#fff; color:#374151 }
.cal2__cuerpo{ flex:1; min-height:0; display:flex; flex-direction:column; margin:7px 8px 8px; background:#fff; border:1px solid #E5E7EB; border-radius:7px; overflow:hidden }
.cal2__wds{ display:grid; grid-template-columns:repeat(7,minmax(0,1fr)); border-bottom:1px solid #E5E7EB }
.cal2__wd{ padding:4px 0; text-align:center; font-size:8.5px; font-weight:600; color:#374151 }
.cal2__grid{ flex:1; min-height:0; display:grid; grid-template-columns:repeat(7,minmax(0,1fr)); grid-template-rows:repeat(5,minmax(0,1fr)) }
.cal2__d{ border-right:1px solid #E5E7EB; border-bottom:1px solid #E5E7EB; padding:2px 3px; display:flex; flex-direction:column; gap:2px; min-width:0; overflow:hidden }
.cal2__d:nth-child(7n){ border-right:0 }
.cal2__d:nth-child(n+29){ border-bottom:0 }
.cal2__d--otro{ background:#F9FAFB }
.cal2__dh{ display:flex; align-items:center; justify-content:space-between }
.cal2__num{ font-size:8.5px; font-weight:500; color:#1F2937 }
.cal2__d--otro .cal2__num{ color:#9CA3AF }
.cal2__d--hoy .cal2__num{ color:var(--app-blue); font-weight:700 }
.cal2__n{ font-size:7px; padding:0 4px; border-radius:999px; background:var(--app-blue-wash); color:var(--app-blue); font-weight:500 }
.cal2__e{ display:flex; flex-direction:column; padding:1px 3px; border-radius:3px; color:#fff; min-width:0; overflow:hidden; line-height:1.15 }
.cal2__e b{ font-size:6.5px; font-weight:600; white-space:nowrap; overflow:hidden; text-overflow:ellipsis }
.cal2__e i{ font-style:normal; font-size:6px; opacity:.9; white-space:nowrap; overflow:hidden; text-overflow:ellipsis }
.cal2__e--abi{ background:#60A5FA } .cal2__e--rev{ background:#F59E0B } .cal2__e--cer{ background:#10B981 }

/* ---------------------------------------------------------
   COMPARTIR CON UN PROVEEDOR — «Comparte tareas con proveedores y clientes».
   Un campo desplegable de proveedor y, a su lado, un botón redondo con un
   sobre. Reloj de 14 s: se abre la lista, se elige «Ernesto», se pulsa el
   sobre y aparece «Compartido». El cursor va en % del ancho y px del alto
   (las alturas de fila son fijas); si cambian el relleno o los tamaños de
   letra hay que volver a medir sus paradas.
   --------------------------------------------------------- */
.app__comparte{ position:relative; --d:16s; padding-bottom:16px }
.cmp2__fila{ display:flex; align-items:center; gap:9px }
.cmp2__sel{
  position:relative; flex:1; min-width:0;
  display:grid; grid-template-columns:minmax(0,1fr) auto; align-items:center; gap:8px;
  padding:6px 9px; border:1px solid var(--app-line); border-radius:7px; min-height:15px;
  font-size:10.5px;
  animation:cmp2-foco var(--d) ease-out infinite; animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
}
.cmp2__ph,.cmp2__val{ grid-column:1; grid-row:1; min-width:0; display:flex; align-items:center; gap:6px; overflow:hidden; white-space:nowrap }
.cmp2__ph{ color:var(--app-mute); animation:cmp2-ph var(--d) linear infinite; animation-play-state:var(--play,running) }
.cmp2__val{ opacity:0; animation:cmp2-val var(--d) linear infinite; animation-play-state:var(--play,running) }
.cmp2__caret{ grid-column:2; grid-row:1; width:12px; height:12px; color:var(--app-mute) }
@keyframes cmp2-ph{ 0%,35%{opacity:1} 37%,97%{opacity:0} 100%{opacity:1} }
@keyframes cmp2-val{ 0%,35%{opacity:0} 37%,96%{opacity:1} 100%{opacity:0} }
@keyframes cmp2-foco{
  0%,17%{ border-color:var(--app-line-2); box-shadow:none }
  19%,36%{ border-color:var(--app-blue); box-shadow:0 0 0 3px var(--app-blue-wash) }
  38%,100%{ border-color:var(--app-line-2); box-shadow:none }
}
.cmp2__menu{
  position:absolute; left:-1px; right:-1px; top:calc(100% + 4px); z-index:5;
  display:flex; flex-direction:column; padding:4px;
  background:#fff; border:1px solid var(--app-line); border-radius:8px;
  box-shadow:0 12px 26px rgba(50,50,93,.18);
  opacity:0; translate:0 -4px; pointer-events:none;
  animation:cmp2-menu var(--d) ease-out infinite; animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
}
@keyframes cmp2-menu{ 0%,19%{opacity:0; translate:0 -6px; scale:.97} 23%,35%{opacity:1; translate:0 0; scale:1} 37%,100%{opacity:0; translate:0 -6px; scale:.97} }
.cmp2__op{ display:flex; align-items:center; gap:7px; padding:5px 6px; border-radius:5px; font-size:10.5px; color:var(--app-text) }
.cmp2__op--sel{ animation:cmp2-op var(--d) linear infinite; animation-play-state:var(--play,running) }
@keyframes cmp2-op{ 0%,31%{background:transparent} 32%,37%{background:var(--app-blue-wash)} 38%,100%{background:transparent} }
.cmp2__mail{
  flex:none; width:28px; height:28px; border-radius:50%;
  display:grid; place-items:center; background:#fff; color:var(--app-mute);
  box-shadow:inset 0 0 0 1px var(--app-line);
  animation:cmp2-mail var(--d) ease-out infinite; animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
}
.cmp2__mail svg{ width:13px; height:13px }
@keyframes cmp2-mail{
  0%,50%   { background:#fff; color:var(--app-mute); box-shadow:inset 0 0 0 1px var(--app-line); scale:1 }
  52.5%    { scale:.88 }
  55%,60%  { background:var(--app-blue); color:#fff; box-shadow:0 0 0 5px var(--app-blue-wash); scale:1 }
  63%,96%  { background:var(--app-blue); color:#fff; box-shadow:inset 0 0 0 1px var(--app-blue); scale:1 }
  100%     { background:#fff; color:var(--app-mute); box-shadow:inset 0 0 0 1px var(--app-line); scale:1 }
}
.app__comparte .cmp2__pill{ animation:cmp2-pill var(--d) ease-out infinite; animation-play-state:var(--play,running); animation-iteration-count:var(--iter,infinite); animation-fill-mode:forwards; }
@keyframes cmp2-pill{ 0%,57%{opacity:0; translate:0 6px} 63%,96%{opacity:1; translate:0 0} 100%{opacity:0; translate:0 0} }
.cmp2__cur{
  position:absolute; left:0; top:0; z-index:8; width:16px; height:16px; margin:-2px 0 0 -3px;
  opacity:0; pointer-events:none; filter:drop-shadow(rgba(12,32,64,.35) 0 2px 3px);
  animation:cmp2-cur var(--d) cubic-bezier(.45,0,.25,1) infinite; animation-play-state:var(--play,running);
}
.cmp2__cur svg{ width:100%; height:100%; display:block }
@keyframes cmp2-cur{
  0%,6%   { opacity:0; left:90%; top:100%; scale:1 }
  9%      { opacity:1; left:90%; top:100%; scale:1 }
  17%     { opacity:1; left:40%; top:75px; scale:1 }
  18.5%   { opacity:1; left:40%; top:75px; scale:.8 }
  20.5%   { opacity:1; left:40%; top:75px; scale:1 }
  28%     { opacity:1; left:24%; top:var(--o2y); scale:1 }
  32%     { opacity:1; left:22%; top:117px; scale:1 }
  33.5%   { opacity:1; left:22%; top:117px; scale:.8 }
  35.5%   { opacity:1; left:22%; top:117px; scale:1 }
  49%     { opacity:1; left:94.8%; top:75px; scale:1 }
  51%     { opacity:1; left:94.8%; top:75px; scale:.8 }
  53%     { opacity:1; left:94.8%; top:75px; scale:1 }
  68%     { opacity:1; left:80%; top:150px; scale:1 }
  74%,100%{ opacity:0; left:80%; top:150px; scale:1 }
}

/* Bocadillo del desplegable: el pico apunta al campo. */
.app__comparte{ --o2y:147px }
.cmp2__menu::before{
  content:""; position:absolute; top:-5px; left:24px; width:8px; height:8px;
  background:#fff; border-left:1px solid var(--app-line); border-top:1px solid var(--app-line);
  transform:rotate(45deg);
}
.cmp2__op:nth-child(2){ animation:cmp2-op2 var(--d) linear infinite; animation-play-state:var(--play,running); animation-iteration-count:var(--iter,infinite); animation-fill-mode:forwards; }
@keyframes cmp2-op2{ 0%,25%{background:transparent} 26%,30%{background:var(--app-blue-wash)} 31%,100%{background:transparent} }
.cmp2__sel{ background:#fff; box-shadow:0 1px 2px rgba(11,18,32,.06) }
@media (prefers-reduced-motion: reduce){
  .cmp2__cur,.cmp2__menu{ display:none }
  .cmp2__ph,.cmp2__val,.cmp2__mail,.app__comparte .cmp2__pill,.cmp2__sel{ animation:none !important }
  .cmp2__ph{ opacity:0 } .cmp2__val{ opacity:1 } .app__comparte .cmp2__pill{ opacity:1; translate:0 0 }
  .cmp2__mail{ background:var(--app-blue); color:#fff }
}

/* Mismo estado resuelto, para cuando la capacidad del rotador ya terminó su
   vuelta única (ver la nota junto a .scene[data-terminado] en ds/scene.css). */
.scene[data-terminado] .cmp2__cur,
.scene[data-terminado] .cmp2__menu{ display:none }
.scene[data-terminado] .cmp2__ph,
.scene[data-terminado] .cmp2__val,
.scene[data-terminado] .cmp2__mail,
.scene[data-terminado] .app__comparte .cmp2__pill,
.scene[data-terminado] .cmp2__sel{ animation:none !important }
.scene[data-terminado] .cmp2__ph{ opacity:0 }
.scene[data-terminado] .cmp2__val{ opacity:1 }
.scene[data-terminado] .app__comparte .cmp2__pill{ opacity:1; translate:0 0 }
.scene[data-terminado] .cmp2__mail{ background:var(--app-blue); color:#fff }

/* =========================================================
   CUATRO EJEMPLOS DE /ia/, CADA UNO CON SU GESTO (30 sep 2026, 3ª vuelta)
   Sustituye la pantalla compartida que hubo aquí un rato el mismo día —la
   lista de ajustes «Funciones de IA», con la fila activa resaltada y las
   otras colapsadas (`.app__iafnlist`, retirada)—: el cliente la vio y pidió
   algo más realista, que recreara el gesto de cada función en vez de
   nombrarla dos veces. Ver el comentario de la sección en ia/index.html.

   EL ROTADOR ERA DE SEIS Y BAJA A CUATRO EL MISMO DÍA: el título de la
   sección decía «Seis funciones…», y el cliente pidió quitar el número —«no
   menciones que son 6 funciones, porque eso obliga a actualizarlo cada vez
   que salga una nueva»— y enseñar sólo cuatro ejemplos. Se retiran el
   generador de visualizaciones y los informes semanales por planta —los dos
   más cercanos en tema a «Generación de informes con IA», que se queda—;
   quedan los cuatro pilares más distintos entre sí: crear, importar,
   analizar y preguntar. Sus pantallas (`.app__iavis*`, `.app__iased*`)
   salen de este fichero con ellos.

   Ninguna de las cuatro pantallas de abajo está comprobada campo a campo
   contra la aplicación: la función es real, el detalle del gesto es
   representativo, a petición expresa del cliente. */

/* --- 1 · Crear incidencia desde texto -------------------------------------
   Tercera vuelta del cliente. La primera trajo una captura real del aviso de
   macOS («Gmail · New Email from…», con su «Close») para la notificación; la
   segunda pidió el logotipo real de Gmail —no un dibujo propio— y que el
   asunto fuera uno concreto («Incidencia en el pedido Nº67/2026»); ésta pide
   que la ficha del acto B sea «como en la app real en la versión PC», no la
   tarjeta flotante de resumen que había.

   La secuencia sigue en tres tiempos, mismo cruce de opacidad que
   `.planalt`/`.app__iaimp` (8 s, `planalt-a`/`planalt-b`, ds/scene.css) para
   el acto entero; dentro del primer acto, el aviso y el «analizando» se
   turnan en el mismo sitio, no se apilan.

   LA FICHA (`.app__incficha`) SIGUE LA ANATOMÍA REAL DE
   `/incidents/incident-details/<id>` —esta vez comprobada navegando la
   propia demo (Ultimate Demo 6, `UTD26_249_001`), no sólo con la ficha
   escrita del MCP—: cabecera con código, estado y prioridad, más una fila
   de iconos; la barra de clasificación con sus tres etiquetas de color
   (centro en azul, categoría en verde, tipo de producto en lila); y debajo,
   en dos columnas, el campo 1 «Descripción» —una caja de texto ancha— y el
   campo 2 «Foto(s)» —una caja más estrecha para adjuntos—, que son SIEMPRE
   los dos primeros campos de cualquier plantilla de incidencia y van
   siempre en esa fila de dos, antes que el resto. El resto de campos —3 en
   adelante— caen debajo en una rejilla de tres columnas, cada uno con su
   círculo de número (el mismo azul que ya usa el asistente de alta,
   `.app__pregn`) y su valor dentro de una caja con borde, como el propio
   formulario. El código con sufijo «REC» —Reclamación— sigue el patrón
   verificado (`<prefijo><año>_<contador>_<sufijo de departamento>`), con un
   contador de ejemplo, sin comprobar contra un caso real. */
.app__iacrea{ position:relative; flex:1; min-height:0 }
.app__iacrea__act{ position:absolute; inset:0; padding:14px; display:flex }
.app__iacrea__act--mail{
  animation:planalt-a 8s ease-in-out infinite;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
}
.app__iacrea__act--res{
  padding:0; opacity:0; align-items:flex-start;
  animation:planalt-b 8s ease-in-out infinite;
  animation-play-state:var(--play,running);
}

/* El aviso, calcado del centro de notificaciones: icono, título y asunto en
   una línea que se corta con puntos suspensivos, «Cerrar» a la derecha.
   Entra deslizándose desde arriba, se lee, y se va —no lo cierra nadie, se
   va solo, que es lo que hace un aviso real pasado su tiempo—. El icono es
   el logotipo real de Gmail (`assets/ia/gmail-icon.webp`, ver
   assets/ia/FUENTES.md), no un dibujo propio. */
.app__iamailpop{
  position:absolute; left:14px; right:14px; top:-46px;
  display:flex; align-items:center; gap:9px;
  padding:9px 11px;
  background:#fff;
  border-radius:13px;
  box-shadow:0 14px 28px rgba(11,18,32,.16), 0 2px 6px rgba(11,18,32,.08);
  opacity:0;
  animation:app-iamailpop 8s ease-in-out infinite;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
}
/* `.app img{width:auto;height:auto;max-width:none}` (0-1-1) le gana a una
   clase suelta (0-1-0) —es la sexta vez que este patrón muerde en este
   repositorio—: se ancla con `.app .app__iamailpopico` (0-2-0). */
.app .app__iamailpopico{ flex:none; width:22px; height:22px; object-fit:contain }
.app__iamailpopbody{ min-width:0; flex:1 }
.app__iamailpopttl{ margin:0; font:600 11px/1.2 Roboto, sans-serif; color:var(--app-text) }
.app__iamailpopsub{
  margin:1px 0 0; font:400 10px/1.3 Roboto, sans-serif; color:var(--app-text-2);
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
}
.app__iamailpopclose{ flex:none; font:500 10px/1.3 Roboto, sans-serif; color:var(--app-mute) }
@keyframes app-iamailpop{
  0%,2%    { opacity:0; top:-46px }
  8%,20%   { opacity:1; top:14px }
  26%,100% { opacity:0; top:-46px }
}

/* «Solved AI» analizando: entra en el mismo hueco, cuando el aviso ya se ha
   ido, no antes. */
.app__iacreaia{
  position:absolute; left:0; right:0; top:50%; translate:0 -50%;
  margin:0; display:flex; align-items:center; justify-content:center; gap:7px;
  font:500 11px/1.3 Roboto, sans-serif; color:var(--app-blue-deep);
  opacity:0;
  animation:app-iacrea-ia 8s ease-in-out infinite;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
}
.app__iacreaia svg{
  width:13px; height:13px; flex:none;
  animation:app-iacrea-spin 1s linear infinite;
  animation-play-state:var(--play,running);
}
@keyframes app-iacrea-ia{ 0%,28%{opacity:0} 34%,40%{opacity:1} 42%,100%{opacity:0} }
@keyframes app-iacrea-spin{ to{ rotate:360deg } }

/* La ficha de la incidencia. Alto natural, no `height:100%` —esa era la
   causa real del corte que vio el cliente—: el acto (`.app__iacrea__act`)
   mide 468px por la proporción del propio marco de navegador, pero la
   escena que lo recorta (el patrón `dev-br` que comparten los cuatro
   paneles de este rotador) sólo enseña ~380px de esa altura; el resto es
   sangrado a propósito, y hasta ahora era hueco vacío del resto de
   pantallas. Con `height:100%` la ficha se estiraba a los 468px enteros y
   el pie «Completado por Solved AI» —empujado al fondo con
   `margin-top:auto`— caía siempre fuera de esa ventana, invisible del
   todo. Con alto natural (y `align-items:flex-start` en el acto, para que
   no la vuelva a estirar el propio flex) el contenido mide lo que pesa de
   verdad y encaja entero. */
.app__incficha{
  width:100%;
  padding:12px 14px;
  display:flex; flex-direction:column; gap:7px;
}

/* Cabecera: código + estado + prioridad, con la fila de iconos empujada al
   final de la MISMA línea —no una fila propia debajo, que en la app real es
   como cabe todo sin que la ficha sea más alta de lo que hace falta—. */
.app__incfichahd{ display:flex; flex-wrap:wrap; align-items:center; gap:6px }
.app__incficons{ margin-left:auto; display:flex; gap:7px; color:var(--app-mute) }
.app__incficons svg{ width:12px; height:12px; display:block }

/* Los chips —código, estado, prioridad arriba; centro, categoría, tipo de
   producto en la barra de clasificación— son la MISMA pieza con distinto
   color, calcada de la app real: fondo muy claro del color, borde del
   mismo color más saturado, texto a juego. El código es la única variante
   sólida —fondo azul lleno, texto blanco—, como en la cabecera real. */
.app__incchip{
  display:inline-flex; align-items:center; gap:3px;
  padding:2px 7px;
  border-radius:999px;
  border:1px solid transparent;
  font:500 9px/1.5 Roboto, sans-serif;
  white-space:nowrap;
}
.app__incchip svg{ width:8px; height:8px; flex:none }
.app__incchip--id{ background:var(--app-blue); border-color:var(--app-blue); color:#fff; font-weight:600 }
.app__incchip--abierta{ background:var(--app-blue-wash); border-color:rgba(30,107,255,.35); color:var(--app-blue-deep) }
.app__incchip--alta{ background:#FBD5D5; border-color:rgba(224,49,49,.35); color:#B02020 }
.app__incchip--centro{ background:var(--app-blue-wash); border-color:rgba(30,107,255,.3); color:var(--app-blue-deep) }
.app__incchip--cat{ background:#E4F6EA; border-color:rgba(18,129,59,.3); color:#12813B }
.app__incchip--tipo{ background:rgba(124,58,237,.1); border-color:rgba(124,58,237,.3); color:var(--app-registro) }

/* La barra de clasificación: «Centro: [chip]  Categoría: [chip]  Tipo:
   [chip]», etiqueta en negrita seguida de su chip, como en la app real. */
.app__incclass{
  margin:0; display:flex; flex-wrap:wrap; align-items:center; gap:5px 10px;
  font:500 9.5px/1.4 Roboto, sans-serif; color:var(--app-text);
}
.app__incclass b{ font-weight:500 }
.app__incclass > span{ display:inline-flex; align-items:center; gap:5px }

/* Los campos 1 y 2 —Descripción y Foto(s)— van SIEMPRE en su propia fila de
   dos columnas, antes que el resto: es así en cualquier plantilla de
   incidencia de la app real, no una elección de esta maqueta. Descripción
   se lleva el ancho porque es el único campo de texto largo de la ficha;
   Foto(s) es la zona de adjuntos, más estrecha. */
.app__incbody{ display:grid; grid-template-columns:1.7fr 1fr; gap:8px }
.app__incdesc,.app__incfotos{ display:flex; flex-direction:column; gap:4px; min-width:0 }
.app__incbox{
  border:1px solid var(--app-line-2);
  border-radius:6px;
  background:#fff;
  font:400 10px/1.4 Roboto, sans-serif; color:var(--app-text);
}
.app__incbox--desc{ padding:6px 8px; min-height:34px }
.app__incbox--foto{
  flex:1; min-height:34px;
  display:flex; flex-direction:column; align-items:center; justify-content:center; gap:2px;
  padding:5px;
  color:var(--app-mute);
  text-align:center;
}
.app__incbox--foto svg{ width:14px; height:14px }
.app__incbox--foto span{ font-size:8.5px; line-height:1.3; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; max-width:100% }

/* El resto de campos —3 en adelante—, en la rejilla de tres columnas de la
   app real, cada uno con su número y su valor dentro de una caja con
   borde, como el propio formulario: sólo los desplegables
   (`.app__incbox--select`) llevan la flechita.

   VA EN SU PROPIO ESPACIO DE NOMBRES (`.app__incgrid*`): `.app__incfieldn`
   —el círculo del número— es el único que comparte con el resto de esta
   misma ficha (`.app__inclabel` lo reutiliza en la descripción y la foto,
   arriba). */
.app__incgrid{ display:grid; grid-template-columns:repeat(3,1fr); gap:8px 8px; margin-top:1px }
.app__incgriditem{
  display:flex; flex-direction:column; gap:4px; min-width:0;
  opacity:0; translate:0 4px;
  animation:res-row-in 8s ease-out infinite;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
}
.app__incgriditem:nth-child(1){ animation-delay:4.3s }
.app__incgriditem:nth-child(2){ animation-delay:4.7s }
.app__incgriditem:nth-child(3){ animation-delay:5.1s }
.app__inclabel{
  margin:0; display:flex; align-items:center; gap:5px;
  font:500 9px/1.3 Roboto, sans-serif; color:var(--app-text-2);
}
.app__incfieldn{
  flex:none; width:14px; height:14px; border-radius:4px;
  display:grid; place-items:center;
  background:var(--app-blue); color:#fff;
  font:500 8px/1 Roboto, sans-serif;
}
.app__incbox--field{ padding:5px 8px }
.app__incbox--select{ position:relative; padding-right:16px; color:var(--app-text) }
.app__incbox--select::after{
  content:"";
  position:absolute; right:7px; top:50%; translate:0 -60%;
  width:0; height:0;
  border-left:3px solid transparent; border-right:3px solid transparent;
  border-top:3.5px solid var(--app-mute);
}
.app__incbox--muted{ color:var(--app-mute) }

/* Quién ha rellenado la ficha: justo debajo de los campos, con el mismo
   hairline que separa el resto de sus bloques. `margin-top` fijo, no
   `auto`: con el alto natural de `.app__incficha` ya no hay una caja de
   468px que rellenar empujando el pie al fondo. */
.app__iacreaai{
  margin-top:8px; padding-top:7px;
  border-top:1px solid var(--app-line);
  display:flex; align-items:center; gap:5px;
  font:400 10px/1.3 Roboto, sans-serif; color:var(--app-mute);
  opacity:0;
  animation:res-row-in 8s ease-out infinite;
  animation-delay:5.6s;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
}
.app__iacreaai svg{ width:11px; height:11px; flex:none; color:var(--app-blue) }

/* EN MÓVIL EL CORTE VOLVÍA (30 sep 2026, segunda vuelta): el alto natural de
   arriba resuelve el caso de escritorio, pero el panel de este rotador es
   `aspect-ratio:1/1` por debajo de 900px (`ds/sections.css`) y el `dev-br` de
   aquí sigue con el 104 % general de `ds/scene.css` —pensado para un aparato
   apaisado dentro de una tarjeta ancha, no para un cuadrado estrecho—: a
   320px de viewport sólo quedan 152px reales de pantalla y la ficha pesa 211,
   así que el pie «Completado por Solved AI» —el mismo que ya se había
   perdido una vez, ver la nota de arriba— vuelve a caer fuera. Comprobado con
   `getBoundingClientRect()` del acto contra el hueco real bajo la barra
   superior, en el mismo eje (nada de mezclar con `scrollHeight`, que aquí no
   refleja el `zoom` que ya aplica `.app:not(.app--movil)` a esta escala y da
   una cuenta falsa). Sólo pasa por debajo de 400px —a 410 ya sobra hueco—:
   el aparato se ensancha ahí, con el mismo mecanismo `dev-br` que ya sangra
   132 % en la fila mixta, sin tocar la ficha ni su contenido. */
@media (max-width:400px){
  #rot-crea .scene__media[data-crop="dev-br"]{ width:140% }
}

@media (prefers-reduced-motion:reduce){
  .app__iacrea__act--mail{ animation:none; opacity:0 }
  .app__iacrea__act--res{ animation:none; opacity:1 }
  .app__iamailpop{ animation:none; opacity:0 }
  .app__iacreaia{ animation:none; opacity:0 }
  .app__iacreaia svg{ animation:none }
  .app__incgriditem{ animation:none; opacity:1; translate:0 0 }
  .app__iacreaai{ animation:none; opacity:1 }
}

/* Mismo estado resuelto, para cuando la capacidad del rotador ya terminó su
   vuelta única (ver la nota junto a .scene[data-terminado] en ds/scene.css). */
.scene[data-terminado] .app__iacrea__act--mail{ animation:none; opacity:0 }
.scene[data-terminado] .app__iacrea__act--res{ animation:none; opacity:1 }
.scene[data-terminado] .app__iamailpop{ animation:none; opacity:0 }
.scene[data-terminado] .app__iacreaia{ animation:none; opacity:0 }
.scene[data-terminado] .app__iacreaia svg{ animation:none }
.scene[data-terminado] .app__incgriditem{ animation:none; opacity:1; translate:0 0 }
.scene[data-terminado] .app__iacreaai{ animation:none; opacity:1 }

/* --- 2 · Importación automática de checklists ------------------------------
   Rehecha el 30 de septiembre de 2026, a petición del cliente: antes el acto
   A ya enseñaba el fichero puesto dentro de la zona de soltar —el arrastre
   no se veía— y el acto B enseñaba el catálogo de tipos de campo (Número,
   Seleccionable…), no una plantilla rellena. Ahora son dos actos con el
   mismo cruce de opacidad que `.planalt` (ds/scene.css, `planalt-a`/
   `planalt-b`, mismo reloj de 8 s), y DENTRO del acto A hay dos tiempos más,
   mismo patrón que el acto A de «Crear incidencia desde texto» (el aviso de
   Gmail seguido de «Solved AI · Analizando…»): primero se arrastra el
   documento hasta la zona de soltar, luego «Solved AI · Creando la
   plantilla…» —reutiliza tal cual `app-iacrea-ia`/`app-iacrea-spin`, mismo
   fotograma 34-40 %—. El acto B ya no es el catálogo de tipos: es la
   plantilla de registro montada, con su responsable, sus controles en OK/KO
   y su firma, que es lo que de verdad sale de importar un procedimiento.

   SEGUNDA VUELTA, EL MISMO DÍA: EL ACTO B SE REHACE CONTRA LA PANTALLA REAL.
   Se abrió un registro cerrado en `demo.trysolved.com/checklist` (Control
   Almacenes, 26_204) para ver cómo pinta la app un control ya respondido, y
   no se parecía a lo que había aquí: (1) un control resuelto no lleva los
   DOS botones OK/KO uno al lado del otro —esa pareja es la que ya usa el
   resto del sitio (`.app__opt`/`.app__opts`, en la home) y sigue siendo la
   correcta para "elegir", pero la app real, una vez respondido, pinta un
   punto de color y UNA sola píldora con el valor, con la fila entera
   teñida—; (2) el responsable no es un control más de la lista: vive en su
   propio bloque "Asignaciones", separado de las secciones; (3) la cabecera
   lleva un resumen "OK: n% · KO: n%" que es lo primero que se lee. Los tres
   cambian aquí. */
.app__iaimp{ position:relative; flex:1; min-height:0 }
.app__iaimp__act{ position:absolute; inset:0; padding:14px; display:flex }
.app__iaimp__act--drop{
  position:absolute; align-items:center; justify-content:center;
  animation:planalt-a 8s ease-in-out infinite;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
}
.app__iaimp__act--res{
  opacity:0; align-items:flex-start;
  animation:planalt-b 8s ease-in-out infinite;
  animation-play-state:var(--play,running);
}
.app__iaimp__act--res .res{ width:100% }

/* El resumen de la cabecera, junto al nombre de la plantilla —como el
   "OK: 95%  KO: 5%" que la app pinta arriba del todo de un registro—. Fijo
   desde que arranca el acto: en la app no es un campo que se rellena, es la
   cuenta de lo que ya se ha respondido. */
.app__iaimpresumen{
  margin:5px 0 0; display:flex; gap:10px;
  font:500 10px/1.3 Roboto, sans-serif;
}
.app__iaimpresumen span{ display:inline-flex; align-items:center; gap:4px }
.app__iaimpresumen span::before{ content:""; width:6px; height:6px; border-radius:50% }
.app__iaimpresumen--ok{ color:#12813B }
.app__iaimpresumen--ok::before{ background:#12813B }
.app__iaimpresumen--ko{ color:#B02020 }
.app__iaimpresumen--ko::before{ background:#B02020 }

/* El responsable, aparte de los controles —como el bloque "Asignaciones" de
   la app, que va antes de las secciones—, no como una fila más de la lista. */
.app__iaimpresp{
  margin:10px 0 0; padding-top:9px;
  border-top:1px solid var(--app-line);
  display:flex; align-items:center; gap:7px;
  font:400 10.5px/1.3 Roboto, sans-serif; color:var(--app-text-2);
  opacity:0;
  animation:res-row-in 8s ease-out infinite;
  animation-delay:4.3s;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
}
.app__iaimpresp b{ font-weight:500; color:var(--app-text) }

/* TERCERA VUELTA: EL PUNTO DE COLOR TAMPOCO ERA LO QUE PINTA LA APP.
   Vuelto a comprobar en vivo, esta vez sobre un control ya respondido de
   verdad —`checklist/preview/<id>/section/<n>`, la fila colapsada, no el
   resumen de la cabecera de la sección—: cada control lleva SU PROPIO
   número —el mismo círculo que ya usa el asistente de alta de incidencias
   (`.app__pregn`) y la ficha de «Crear incidencia desde texto»
   (`.app__incfieldn`), en verde o rojo según OK/KO—, la etiqueta en negrita
   y el valor debajo en una segunda línea, y a la derecha una píldora
   «Completado» —siempre verde, con su check: dice que el control tiene
   respuesta, no cuál es—. El punto de color y la píldora con el valor de
   la vuelta anterior no existen en la app; esto sí. */
.app__iaimprows{ margin-top:8px; display:flex; flex-direction:column; gap:7px }
.app__iaimprow{
  display:flex; align-items:flex-start; gap:7px;
  opacity:0; translate:0 4px;
  animation:res-row-in 8s ease-out infinite;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
}
.app__iaimprow:nth-child(1){ animation-delay:4.7s }
.app__iaimprow:nth-child(2){ animation-delay:5.1s }
.app__iaimprown{
  flex:none; width:15px; height:15px; margin-top:1px; border-radius:4px;
  display:grid; place-items:center;
  font:500 8.5px/1 Roboto, sans-serif;
  background:#fff; border:1px solid var(--c); color:var(--c);
}
/* `.app__iaimprowbody` NO lleva `flex:1`: con el recorte `dev-br` de este
   rotador sólo se ve una franja del ancho real del dispositivo (~585px de
   los 697 reales), y un `flex:1` que reparte el ancho entero empuja lo que
   va detrás —aquí, la píldora «Completado»— más allá de esa franja: caía
   invisible, con su borde izquierdo ya fuera del recorte, comprobado con
   `javascript_tool`. Es la tercera vez que este patrón muerde en esta
   misma pantalla. La píldora va DETRÁS del valor, en la misma línea, no
   empujada al margen derecho de una fila que no se ve entera. */
.app__iaimprowbody{ min-width:0 }
.app__iaimprowl{ margin:0; font:500 10px/1.3 Roboto, sans-serif; color:var(--app-text) }
.app__iaimprowv{
  margin:2px 0 0; display:flex; align-items:center; flex-wrap:wrap; gap:6px;
  font:400 10px/1.3 Roboto, sans-serif; color:var(--c);
}
.app__iaimprowdone{
  flex:none; display:inline-flex; align-items:center; gap:3px;
  padding:2px 7px; border-radius:999px;
  background:#E4F6EA; border:1px solid rgba(18,129,59,.3); color:#12813B;
  font:500 8.5px/1.4 Roboto, sans-serif; white-space:nowrap;
}
.app__iaimprowdone svg{ width:8px; height:8px; flex:none }
.app__iaimprow--ok{ --c:#12813B }
.app__iaimprow--ko{ --c:#B02020 }

/* La zona de soltar. Se queda igual el resto del ciclo —iconos y rótulo,
   fondo de referencia—, y sólo desaparece cuando le toca el turno a «Solved
   AI · Creando la plantilla…» (26-32 %, justo antes de que ese texto se haga
   visible al 34 %). */
.app__iaimpdrop{
  width:100%; max-width:220px;
  padding:13px 12px 11px;
  border:1.4px dashed var(--app-line-2);
  border-radius:12px;
  background:var(--app-blue-wash);
  text-align:center;
  box-shadow:0 0 0 0 rgba(30,107,255,0);
  animation:app-iaimp-hover 8s ease-in-out infinite, app-iaimpdrop-out 8s ease-in-out infinite;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
}
@keyframes app-iaimp-hover{
  0%,8%    { box-shadow:0 0 0 0 rgba(30,107,255,0) }
  14%,20%  { box-shadow:0 0 0 3px var(--app-blue-wash) }
  26%,100% { box-shadow:0 0 0 0 rgba(30,107,255,0) }
}
@keyframes app-iaimpdrop-out{ 0%,26%{opacity:1} 32%,100%{opacity:0} }
.app__iaimpicos{ display:flex; justify-content:center; gap:7px; margin-bottom:8px }
.app__iaimpicos svg{ width:22px; height:16px; border-radius:3px; display:block }
.app__iaimpt{ margin:0; font:500 11px/1.3 Roboto, sans-serif; color:var(--app-text) }
.app__iaimpu{ margin:3px 0 0; font:400 9.5px/1.3 Roboto, sans-serif; color:var(--app-mute) }

/* El fichero, dentro de la zona: no está puesto desde el fotograma 0, entra
   cuando el documento que se arrastra (`.app__iaimpdrag`, más abajo) llega y
   se suelta —18-22 %—. La barra de subida llena justo después. */
.app__iaimpfich{
  display:flex; align-items:center; gap:6px;
  margin-top:10px; padding:6px 8px;
  background:#fff; border:1px solid var(--app-line); border-radius:8px;
  text-align:left;
  opacity:0;
  animation:app-iaimp-fich 8s ease-out infinite;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
}
@keyframes app-iaimp-fich{
  0%,18%  { opacity:0; translate:0 4px }
  22%,32% { opacity:1; translate:0 0 }
  36%,100%{ opacity:0; translate:0 0 }
}
.app__iaimpfich svg{ width:16px; height:12px; flex:none; border-radius:2px }
.app__iaimpfich span{
  min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
  font:400 10px/1.2 Roboto, sans-serif; color:var(--app-text);
}
.app__iaimpbarra{
  margin-top:8px; height:4px; border-radius:3px; background:var(--app-line); overflow:hidden;
  opacity:0;
  animation:app-iaimp-fich 8s ease-out infinite;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
}
.app__iaimpbarra i{
  display:block; height:100%; width:0; border-radius:3px;
  background:var(--app-blue);
  animation:app-iaimp-barra 8s ease-in-out infinite;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
}
@keyframes app-iaimp-barra{ 0%,22%{width:0} 32%,100%{width:100%} }

/* EL ARRASTRE. Una tarjeta suelta con el mismo icono y nombre que el fichero
   de dentro —es el mismo documento, sólo que todavía en el aire—: nace fuera
   de la zona, en un ángulo, y se desplaza hasta el centro de la zona de
   soltar según avanza el arrastre; al llegar se queda quieta un instante
   (el "hover" antes de soltar, que es cuando `.app__iaimpdrop` enciende su
   halo) y se apaga justo cuando `.app__iaimpfich` la sustituye. */
.app__iaimpdrag{
  position:absolute; left:50%; top:50%; z-index:2;
  display:flex; align-items:center; gap:6px;
  padding:6px 9px;
  background:#fff; border:1px solid var(--app-line); border-radius:8px;
  box-shadow:0 10px 22px rgba(11,18,32,.16);
  font:400 10px/1.2 Roboto, sans-serif; color:var(--app-text);
  pointer-events:none;
  opacity:0;
  animation:app-iaimp-drag 8s ease-out infinite;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
}
.app__iaimpdrag svg{ width:16px; height:12px; flex:none; border-radius:2px }
.app__iaimpdrag span{ white-space:nowrap }
@keyframes app-iaimp-drag{
  0%,3%   { opacity:0; translate:-72px -50px; rotate:-8deg }
  9%      { opacity:1; translate:-72px -50px; rotate:-8deg }
  20%,24% { opacity:1; translate:-50% -50%;   rotate:0deg }
  28%,100%{ opacity:0; translate:-50% -50%;   rotate:0deg }
}

/* «Solved AI · Creando la plantilla…»: mismo hueco centrado que ocupaba la
   zona de soltar, mismo icono y giro que «Analizando el correo…» del panel
   de al lado —reutiliza sus fotogramas tal cual, así que va en el mismo
   tramo del reloj (34-40 %) y no hace falta un fotograma nuevo—. */
.app__iaimpia{
  position:absolute; left:0; right:0; top:50%; translate:0 -50%;
  margin:0; display:flex; align-items:center; justify-content:center; gap:7px;
  font:500 11px/1.3 Roboto, sans-serif; color:var(--app-blue-deep);
  opacity:0;
  animation:app-iacrea-ia 8s ease-in-out infinite;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
}
.app__iaimpia svg{
  width:13px; height:13px; flex:none;
  animation:app-iacrea-spin 1s linear infinite;
  animation-play-state:var(--play,running);
}

/* La firma, al pie, después de responsable y controles —último dato del
   registro, y el mismo bloque de cierre que ya usa `.app__sign` en otras
   escenas del sitio (icono + "Firmado por…")—. */
.app__iaimpfirma{
  margin:9px 0 0; padding-top:8px;
  border-top:1px solid var(--app-line);
  display:flex; align-items:center; gap:6px;
  font:400 10.5px/1.3 Roboto, sans-serif; color:var(--app-text-2);
  opacity:0;
  animation:res-row-in 8s ease-out infinite;
  animation-delay:5.6s;
  animation-play-state:var(--play,running);
  animation-iteration-count:var(--iter,infinite);
  animation-fill-mode:forwards;
}
.app__iaimpfirma svg{ width:12px; height:12px; flex:none; color:var(--app-blue) }

@media (prefers-reduced-motion:reduce){
  .app__iaimp__act--drop{ animation:none; opacity:0 }
  .app__iaimp__act--res{ animation:none; opacity:1 }
  .app__iaimpdrop{ animation:none; box-shadow:none }
  .app__iaimpdrag{ animation:none; opacity:0 }
  .app__iaimpfich,.app__iaimpbarra{ animation:none; opacity:1; translate:0 0 }
  .app__iaimpbarra i{ animation:none; width:100% }
  .app__iaimpia{ animation:none; opacity:0 }
  .app__iaimpia svg{ animation:none }
  .app__iaimpresp{ animation:none; opacity:1 }
  .app__iaimprow{ animation:none; opacity:1; translate:0 0 }
  .app__iaimpfirma{ animation:none; opacity:1 }
}

/* Mismo estado resuelto, para cuando la capacidad del rotador ya terminó su
   vuelta única (ver la nota junto a .scene[data-terminado] en ds/scene.css). */
.scene[data-terminado] .app__iaimp__act--drop{ animation:none; opacity:0 }
.scene[data-terminado] .app__iaimp__act--res{ animation:none; opacity:1 }
.scene[data-terminado] .app__iaimpdrop{ animation:none; box-shadow:none }
.scene[data-terminado] .app__iaimpdrag{ animation:none; opacity:0 }
.scene[data-terminado] .app__iaimpfich,
.scene[data-terminado] .app__iaimpbarra{ animation:none; opacity:1; translate:0 0 }
.scene[data-terminado] .app__iaimpbarra i{ animation:none; width:100% }
.scene[data-terminado] .app__iaimpia{ animation:none; opacity:0 }
.scene[data-terminado] .app__iaimpia svg{ animation:none }
.scene[data-terminado] .app__iaimpresp{ animation:none; opacity:1 }
.scene[data-terminado] .app__iaimprow{ animation:none; opacity:1; translate:0 0 }
.scene[data-terminado] .app__iaimpfirma{ animation:none; opacity:1 }
