/* Hoja base de /app/, trozo 02 — su index enlaza los trozos en orden, y la cascada depende de él. */
/* ===== MARKDOWN (F16, portada) ===== */
/* ==================================================================================================
   MARKDOWN DEL AGENTE (.cuerpo.md, árbol de markdown.js)
   white-space normal: dentro de un párrafo el salto simple colapsa a espacio, que es el markdown
   estándar; el pre-wrap del turno queda para el texto plano (humano/sistema), que sí conserva lo
   tecleado tal cual.
   NINGÚN título del agente se pasa a versalitas: las mayúsculas son de las etiquetas que escribe
   esta interfaz, nunca del texto que escribió otro.
   ================================================================================================== */

.turno .cuerpo.md { white-space: normal; }
.turno .cuerpo.md > :first-child { margin-top: 0; }
.turno .cuerpo.md > :last-child { margin-bottom: 0; }

.md-parrafo { margin: var(--e3) 0; }

.md-titulo1, .md-titulo2, .md-titulo3 {
  margin: var(--e5) 0 var(--e2);
  color: var(--tinta);
}
.md-titulo1 { font-size: var(--t-xl); line-height: var(--lh-xl); font-weight: 700; letter-spacing: var(--track-titulo); }
.md-titulo2 { font-size: var(--t-lg); line-height: var(--lh-md); font-weight: 700; letter-spacing: -.01em; }
.md-titulo3 { font-size: var(--t-md); line-height: var(--lh-md); font-weight: 700; letter-spacing: 0; }

.md-codigo {
  background: var(--hundida); color: var(--tinta);
  border-radius: var(--r-menudo);
  padding: .1em var(--e1);
  font-size: .92em;                 /* relativo: el código en línea no puede desalinear la prosa */
}
.turno.humano .cuerpo .md-codigo { background: var(--fondo); }   /* dentro de la burbuja, que se note */

.md-lista { margin: var(--e3) 0; padding-left: var(--e5); }
.md-lista li { margin: var(--e1) 0; }

/* La cita usa la MISMA raya de margen que el resto del sistema: coherencia por encima de adorno. */
.md-cita {
  margin: var(--e3) 0;
  padding: 0 0 0 var(--e4);
  border-left: var(--carril) solid var(--borde);
  color: var(--tinta-suave);
}

.md-enlace { color: var(--acento); }

/* Bloque de código: cabecera (lenguaje en versalitas + copiar) y pre con scroll-x PROPIO — un
   comando largo no fuerza scroll lateral en toda la línea de tiempo. */
.md-bloque {
  margin: var(--e4) 0;
  border: var(--filete) solid var(--linea); border-radius: var(--r-tarjeta);
  background: var(--hundida); overflow: hidden;
}
.md-bloque-cabecera {
  display: flex; align-items: center; gap: var(--e2);
  padding: var(--e1) var(--e1) var(--e1) var(--e3);
  border-bottom: var(--filete) solid var(--linea);
}
.md-lang {
  font: 600 var(--t-2xs)/var(--lh-2xs) var(--prosa);
  text-transform: uppercase; letter-spacing: var(--track-micro);
  color: var(--tinta-apagada);
}
.md-lang:empty { display: none; }      /* sin lenguaje declarado no se enseña un hueco */
.md-copiar {
  margin-left: auto;                   /* a la derecha aunque .md-lang esté vacía */
  border: 0; background: transparent; color: var(--tinta-suave); cursor: pointer;
  font: 400 var(--t-xs)/var(--lh-xs) var(--prosa);
  padding: var(--e1) var(--e2); border-radius: var(--r-menudo);
  white-space: nowrap;
}
.md-copiar:hover { background: var(--elevada); color: var(--tinta); }
.md-copiar.copiado { color: var(--bien); }
.md-bloque pre {
  margin: 0; padding: var(--e3);
  overflow-x: auto; white-space: pre;
  font-size: var(--t-sm); line-height: var(--lh-md);
  color: var(--tinta);
}


/* ===== TARJETAS DE ACCIÓN (F16, portada) ===== */
/* ==================================================================================================
   TARJETAS DE ACCIÓN — la raya del margen
   Cada tarjeta lleva un CARRIL de 3px pegado a su borde izquierdo, separado del alto por --e3 y
   redondeado por fuera: una marca trazada al margen, no un borde de color que envuelve la caja.
   Existe SIEMPRE (gris cuando no hay nada que decir), así que al cambiar de estado cambia el
   color y no se mueve ni un píxel del contenido.
   Lo caducado y lo rechazado NO se rebajan con opacity — eso rebajaría su contraste medido. Se
   apagan con el gris del carril y con su propia palabra.
   ================================================================================================== */

.tarjeta {
  position: relative;
  background: var(--elevada);
  border: var(--filete) solid var(--linea);
  border-radius: var(--r-tarjeta);
  box-shadow: var(--sombra-1);
  padding: var(--e4);
}
.tarjeta::after {
  content: '';
  position: absolute; left: 0; top: var(--e3); bottom: var(--e3);
  width: var(--carril);
  border-radius: 0 var(--r-chip) var(--r-chip) 0;
  background: var(--marca);
}
.tarjeta[data-estado="pendiente"]   { --marca: var(--alerta); }
.tarjeta[data-estado="ejecutada"]   { --marca: var(--bien); }
.tarjeta[data-estado="fallida"]     { --marca: var(--mal); }
.tarjeta[data-estado="rechazada"],
.tarjeta[data-estado="expirada"]    { --marca: var(--tinta-apagada); }
.tarjeta[data-estado="desconocido"] { --marca: var(--desconocido); }

/* La pendiente es lo único que pide algo AHORA, y aun así NO se le pone el borde entero de su
   color: eso es teñir la caja, no marcarla, y en claro un borde de --alerta (#7a5200) se lee
   marrón alrededor de una tarjeta blanca. Lo que la distingue ya es suficiente y todo es
   contenido: nace ABIERTA (tarjetas.js), su carril es ámbar, su insignia es ámbar y dentro lleva
   la caja de confirmación. El estado se marca; la caja no se pinta. */

.tarjeta > summary, .tarjeta .cab {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--e2) var(--e3);
  cursor: pointer; list-style: none;
}
.tarjeta > summary::-webkit-details-marker { display: none; }
.tarjeta .titulo {
  font-size: var(--t-lg); line-height: var(--lh-md); font-weight: 600;
  letter-spacing: -.01em;
  overflow-wrap: anywhere;
}
/* El orden del DOM es título · insignia · cadena · sello, y la cadena ocupa línea entera; sin
   `order` el sello caía a una TERCERA línea, solo, a la derecha, como una errata. Con esto la
   cabecera es: [título · insignia ······ fecha] y debajo la cadena, que es como se lee. */
.tarjeta > summary .sello { order: 1; margin-left: auto; }
.tarjeta > summary .cadena { order: 2; }

.tarjeta .detalle {
  display: flex; flex-direction: column; gap: var(--e1);
  margin-top: var(--e4); padding-top: var(--e3);
  border-top: var(--filete) solid var(--linea);
  font-size: var(--t-sm); line-height: var(--lh-sm);
  color: var(--tinta-suave);
}
/* El hueco de aviso de la tarjeta solo separa cuando tiene algo dentro. */
.tarjeta .detalle > [data-hueco-aviso]:not(:empty) { margin-top: var(--e2); }

/* -- la cadena de estados: un escalímetro, no una frase ---------------------------------------
   Cada peldaño lleva su punto: hueco = no alcanzado, relleno = alcanzado, relleno y GRANDE =
   aquí estás. Se lee de un vistazo hasta dónde llegó de verdad, que es justo lo que este
   proyecto no permite esconder. Salta a su propia línea (flex-basis 100%) para no pelearse con
   el título. Lo NO alcanzado va en apagado, jamás en verde. */
.cadena {
  flex: 1 0 100%;
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--e2);
  margin-top: var(--e1);
  font-size: var(--t-xs); line-height: var(--lh-xs);
  color: var(--tinta-apagada);
}
.peldano { display: inline-flex; align-items: center; gap: var(--e1); }
.peldano::before {
  content: '';
  width: var(--punto); height: var(--punto); border-radius: var(--r-chip);
  border: var(--trazo-punto) solid currentColor;
}
.peldano.hecho { color: var(--bien); }
.peldano.hecho::before { background: currentColor; }
.peldano.aqui { color: var(--tinta); font-weight: 700; }
.peldano.aqui::before { width: var(--punto-aqui); height: var(--punto-aqui); background: currentColor; }
.sep-cadena { color: var(--tinta-apagada); font-size: var(--t-2xs); }

/* -- insignia: la palabra del estado con su marca de 2px, sin marco y sin relleno --------------
   Versalitas porque es vocabulario de ESTA interfaz (ESTADO_ACCION), no texto de nadie. */
.insignia {
  display: inline-flex; align-items: center; gap: var(--e2);
  font: 700 var(--t-2xs)/var(--lh-2xs) var(--prosa);
  text-transform: uppercase; letter-spacing: var(--track-micro);
  color: var(--tinta-suave);
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
.insignia::after {
  content: '';
  order: -1;                                  /* la marca va DELANTE de la palabra */
  flex: 0 0 auto;
  width: var(--carril-fino); height: 1.1em;
  border-radius: var(--r-chip);
  background: currentColor;
}
.insignia.pendiente   { color: var(--alerta); }
.insignia.ejecutada   { color: var(--bien); }
.insignia.fallida     { color: var(--mal); }
.insignia.expirada,
.insignia.rechazada   { color: var(--tinta-apagada); }
/* REGLA 4 — el color nunca es la única señal. `.dato.desconocido` llevaba su subrayado punteado
   desde F16 y la insignia NO: el mismo estado se distinguía en la franja y no en la tarjeta.
   Lo cazó el generador del design system al aplicar las reglas escritas a una pieza que yo no
   había revisado contra ellas — que es exactamente para lo que sirve escribirlas. */
.insignia.desconocido {
  color: var(--desconocido);
  text-decoration: underline dotted; text-underline-offset: 3px;
}

/* -- confirmación inline: el control más serio de la app ---------------------------------------
   Se hunde en su propia caja para que se lea como «aquí se teclea algo que ejecuta», y la frase
   se enseña en mono sobre superficie elevada. La frase se TECLEA: sin autocompletar, a propósito
   (regla del proyecto, no decisión de diseño). */
.confirmar {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--e2);
  margin-top: var(--e3);
  padding: var(--e3);
  background: var(--hundida);
  border: var(--filete) solid var(--linea);
  border-radius: var(--r-tarjeta);
}
.confirmar input {
  flex: 1; min-width: 220px;
  background: var(--elevada); color: var(--tinta);
  border: var(--filete) solid var(--borde); border-radius: var(--r-control);
  padding: var(--e2) var(--e3);
  font: 400 var(--t-md)/var(--lh-md) var(--mono);
  outline: none;
}
/* F18 — en el teléfono, EJECUTAR ocupa su fila entera a 44px: es el gesto más serio de la app y
   las cuatro maquetas lo dibujan a ancho completo. En pantalla grande vuelve a su tamaño (abajo,
   en la capa de 48em): las dos mitades existen porque esta hoja es mobile-first. */
.confirmar button.primario { flex: 1 1 auto; min-height: var(--alto-tactil); }

.frase-esperada {
  flex-basis: 100%;
  color: var(--tinta-suave);
  font-size: var(--t-sm); line-height: var(--lh-lg);
}
.frase-esperada code {
  background: var(--elevada); color: var(--tinta);
  border: var(--filete) solid var(--linea);
  padding: var(--e1) var(--e2); border-radius: var(--r-menudo);
  font-size: var(--t-md); letter-spacing: .01em;
  user-select: text;
  /* La frase es larga y en un teléfono parte en dos líneas. Sin esto, el trozo de abajo pierde
     el padding y el radio y la frase se lee como dos rectángulos rotos — justo el texto que hay
     que copiar con los ojos y teclear sin un carácter de más. */
  -webkit-box-decoration-break: clone;
          box-decoration-break: clone;
}

/* F18 — la CABECERA del hilo (sesion.js, compartido: su gemelo vive en ui.css). Título grande y
   una línea de meta. Va DENTRO de .tiempo, así hereda la columna de 780px y no exige nodo nuevo
   en ningún armazón. El respiro de abajo la separa del primer turno sin regla dibujada: en el
   rediseño las zonas se separan por aire. */
.hilo-cab { margin-bottom: var(--e4); }
.hilo-titulo {
  margin: 0 0 var(--e1);
  font: 600 var(--t-2xl)/var(--lh-2xl) var(--prosa); color: var(--tinta);
  letter-spacing: var(--track-titulo);
  overflow-wrap: anywhere;
}
.hilo-meta {
  font: 400 var(--t-sm)/var(--lh-sm) var(--prosa); color: var(--tinta-apagada);
}

/* F18 — la anatomía de la tarjeta de acción, del rediseño. El QUÉ es el protagonista y va en
   mono grande: la persona confirma un comando, no un párrafo. El riesgo es un chip con su PALABRA
   —el color solo acompaña— y los metadatos una línea apagada. Las clases nacen en tarjetas.js,
   que es compartido: su gemelo de /ui/ vive en ui.css, y la suite exige que existan en las dos. */
.accion-cab {
  display: flex; align-items: flex-start; justify-content: space-between; gap: var(--e3);
  margin: var(--e2) 0 var(--e1);
}
.accion-que {
  min-width: 0;
  font: 600 var(--t-xl)/var(--lh-xl) var(--mono); color: var(--tinta);
  letter-spacing: -.01em;
  overflow-wrap: anywhere;
}
.accion-riesgo {
  flex: none;
  padding: 3px var(--e2);
  border: var(--filete) solid currentColor; border-radius: var(--r-menudo);
  font: 700 var(--t-2xs)/1 var(--prosa);
  text-transform: uppercase; letter-spacing: var(--track-micro);
  color: var(--tinta-apagada);
}
/* Los niveles que cortan algo llevan el tono de alerta; la PALABRA es la señal, el color acompaña. */
.accion-riesgo.r-destructive, .accion-riesgo.r-production { color: var(--alerta); }
.accion-riesgo.r-read { border-style: dotted; }   /* lectura: chip de trazo suave, no pide nada */
.accion-meta {
  margin-bottom: var(--e2);
  font: 400 var(--t-sm)/var(--lh-sm) var(--prosa); color: var(--tinta-apagada);
}

/* El JSON crudo: scroll PROPIO. Sin esto, un query_ref largo desborda la tarjeta y fuerza scroll
   lateral en toda la línea de tiempo. */
.tarjeta .crudo {
  overflow-x: auto; max-width: 100%;
  background: var(--hundida); color: var(--tinta-suave);
  border: var(--filete) solid var(--linea);
  border-radius: var(--r-control);
  padding: var(--e2) var(--e3);
  margin: var(--e2) 0 0;
  font-size: var(--t-xs); line-height: var(--lh-md);
}


/* ===== AVISOS (F16, portada) ===== */
/* ==================================================================================================
   AVISOS — error ≠ vacío ≠ trabajando. Tres cosas distintas, tres trajes distintos.
   El error es la ÚNICA pieza de la hoja con fondo tintado: la sobriedad que se aplica a todo lo
   demás, aquí haría daño. Lleva además carril, icono y su palabra.
   ================================================================================================== */

.aviso {
  position: relative;
  margin: 0;
  background: var(--elevada);
  border: var(--filete) solid var(--linea); border-radius: var(--r-tarjeta);
  padding: var(--e3) var(--e4);
  font-size: var(--t-sm); line-height: var(--lh-sm);
  color: var(--tinta-suave);
}
.aviso.mal {
  /* NO es flex, y la razón la encontró el generador del design system al escribir un aviso con
     texto y `<code>` mezclados: cada nodo suelto se convertía en un flex item y la frase salía
     partida y recortada. El flex no aportaba nada —el carril del margen es un `::after`
     posicionado, que no participa del flujo— así que era riesgo puro a cambio de cero. Hoy todos
     los avisos se construyen con `textContent` y por eso no se veía; el día que uno lleve un
     enlace o una negrita, se habría visto. */
  background: var(--mal-fondo);
  border-color: var(--mal);
  color: var(--mal);
  overflow-wrap: anywhere;      /* un motivo del servidor trae rutas e ids larguísimos */
}
.aviso.mal::after {
  content: '';
  position: absolute; left: 0; top: var(--e2); bottom: var(--e2);
  width: var(--carril);
  border-radius: 0 var(--r-chip) var(--r-chip) 0;
  background: var(--mal);
}
/* «Trabajando» no es ni bien ni mal: caja de trazo discontinuo, sin fondo. Provisional a la
   vista, sin fingir que ya hay un resultado. */
.aviso.trabajando {
  background: transparent;
  border-style: dashed;
  border-color: var(--borde);
  color: var(--tinta-suave);
}

.vacio {
  color: var(--tinta-apagada);
  font-size: var(--t-md); line-height: var(--lh-md);
}


/* ===== VACÍOS Y CARGANDO (F16, portada) ===== */
/* ==================================================================================================
   VACÍOS Y CARGANDO — los estados que más se miran en una app de administración.
   Un vacío bien hecho explica POR QUÉ está vacío y qué haría que dejara de estarlo; esos textos
   ya los escriben los módulos. Aquí va el traje: aire, línea corta, centrado y un icono dibujado
   a mano y embebido (data-uri + máscara, para que herede el color medido del texto).
   .inv-resumen queda FUERA: ahí `.vacio` no es un estado vacío, es la línea «0 de 12».
   ================================================================================================== */

.vacio:not(.inv-resumen) {
  display: flex; flex-direction: column; align-items: center;
  gap: var(--e3);
  max-width: 44ch;                 /* mucho más corta que la prosa: es un cartel, no un texto */
  margin-inline: auto;
  padding: var(--e7) var(--e4);
  text-align: center;
  text-wrap: pretty;               /* sin líneas huérfanas de una palabra */
  line-height: var(--lh-lg);
}
/* En la barra, el mismo traje con menos aire: 280px no dan para --e7 arriba y abajo. */
.conversaciones .vacio {
  padding: var(--e5) var(--e2);
  font-size: var(--t-sm);
  line-height: var(--lh-md);
}

.cargando {
  display: flex; flex-direction: column; gap: var(--e3);
  color: var(--tinta-apagada);
  font-size: var(--t-sm); line-height: var(--lh-sm);
}
.conversaciones > .cargando { padding: var(--e3) var(--e3) var(--e4); }
.principal > .cargando {
  max-width: var(--ancho-hilo); margin: 0 auto;
  padding: var(--e5) 0 var(--e6);
}

/* ---- iconos y esqueletos, dibujados aquí y embebidos ------------------------------------------
   Van de MÁSCARA y no de background-image por una razón concreta: dentro de un data-uri
   `currentColor` no resuelve (el SVG es otro documento y allí es negro), así que un
   background-image obligaría a mantener dos copias por icono, una por tema. Con máscara el color
   lo pone `background-color: currentColor` y el icono hereda EXACTAMENTE el color ya medido del
   texto al que acompaña. Una sola copia, imposible que se desincronice del tema. */
:root {
  /* Burbuja vacía con la línea de dentro punteada: «aquí se escribe, y todavía no hay nada». */
  --mask-vacio: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'><rect x='3' y='4.5' width='18' height='12' rx='3.5'/><path d='M7.6 16.5v3.7l4.4-3.7'/><path d='M7.8 10.5h8.4' stroke-dasharray='1.6 3.2'/></svg>");

  /* Exclamación en círculo. NO una equis: una equis en la esquina de una caja de color se lee
     como «cerrar esto», y este icono no cierra nada, avisa. Tampoco el triángulo, que en esta
     app ya significa --alerta («necesita atención»), no --mal («ha fallado»). */
  --mask-mal: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='1.8' stroke-linecap='round'><circle cx='12' cy='12' r='9'/><path d='M12 7.4v5.4'/><path d='M12 16.3h.01'/></svg>");

  /* Esqueleto de la lista lateral: 4 filas de título + fecha, con anchos distintos (una pila de
     barras idénticas parece una tabla, no una lista que está llegando). */
  --mask-esq-lista: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 240 116' preserveAspectRatio='none' fill='%23000'><rect y='0' width='168' height='10' rx='5'/><rect y='15' width='56' height='7' rx='3.5'/><rect y='30' width='204' height='10' rx='5'/><rect y='45' width='56' height='7' rx='3.5'/><rect y='60' width='140' height='10' rx='5'/><rect y='75' width='56' height='7' rx='3.5'/><rect y='90' width='186' height='10' rx='5'/><rect y='105' width='56' height='7' rx='3.5'/></svg>");

  /* Esqueleto del hilo: cinco líneas de prosa, todas a la izquierda y neutras. Un esqueleto NO
     dibuja contenido reconocible: uno con forma de burbuja de humano, pintado antes de saber si
     hay sesión, insinuaría mensajes que quizá no existen. Esperar no es tener. */
  --mask-esq-hilo: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 640 120' preserveAspectRatio='none' fill='%23000'><rect y='0' width='596' height='12' rx='6'/><rect y='26' width='632' height='12' rx='6'/><rect y='52' width='540' height='12' rx='6'/><rect y='78' width='616' height='12' rx='6'/><rect y='104' width='408' height='12' rx='6'/></svg>");
}

@supports (mask-image: linear-gradient(#000, #000)) or (-webkit-mask-image: linear-gradient(#000, #000)) {
  .vacio:not(.inv-resumen)::before {
    content: '';
    flex: 0 0 auto;
    width: var(--icono-vacio); height: var(--icono-vacio);
    background-color: currentColor;      /* = --tinta-apagada, medido en ambos temas */
    -webkit-mask-image: var(--mask-vacio);
            mask-image: var(--mask-vacio);
    -webkit-mask-repeat: no-repeat;  mask-repeat: no-repeat;
    -webkit-mask-size: 100% 100%;    mask-size: 100% 100%;
  }
  .aviso.mal::before {
    content: '';
    flex: 0 0 auto;
    /* La caja del icono ES la caja de línea del texto (--t-sm × --lh-sm) y el dibujo va centrado
       dentro: así queda alineado con la primera línea sin un margen mágico de corrección, y
       sigue alineado si un día cambia el tamaño de texto del aviso. */
    width: var(--icono-aviso);
    height: calc(var(--t-sm) * var(--lh-sm));
    background-color: currentColor;       /* = --mal, medido sobre --mal-fondo */
    -webkit-mask-image: var(--mask-mal);
            mask-image: var(--mask-mal);
    -webkit-mask-repeat: no-repeat;  mask-repeat: no-repeat;
    -webkit-mask-position: center;   mask-position: center;
    -webkit-mask-size: var(--icono-aviso); mask-size: var(--icono-aviso);
  }
  /* Fuera de @supports no se genera esqueleto: sin máscaras, .cargando se queda con su texto,
     que es quien de verdad informa. Un bloque de color sin recortar no sería un esqueleto,
     sería una mancha. */
  .conversaciones > .cargando::after,
  .principal > .cargando::after {
    content: '';
    display: block;
    background: linear-gradient(100deg, var(--esq-barra) 0%, var(--esq-brillo) 45%, var(--esq-barra) 80%);
    -webkit-mask-repeat: no-repeat;  mask-repeat: no-repeat;
    -webkit-mask-size: 100% 100%;    mask-size: 100% 100%;
    animation: esqueleto-latido 1.8s ease-in-out infinite;
  }
  .conversaciones > .cargando::after {
    height: var(--esq-alto-lista);
    -webkit-mask-image: var(--mask-esq-lista);
            mask-image: var(--mask-esq-lista);
  }
  .principal > .cargando::after {
    height: var(--esq-alto-hilo);
    -webkit-mask-image: var(--mask-esq-hilo);
            mask-image: var(--mask-esq-hilo);
  }
}

/* El movimiento del esqueleto es un LATIDO de opacidad y no el barrido diagonal de costumbre: el
   barrido exige animar background-position (repintado por fotograma) y esto solo anima lo que
   resuelve el compositor. Quien informa de que se espera es el TEXTO, que no parpadea nunca. */
@keyframes esqueleto-latido {
  0%, 100% { opacity: 1; }
  50%      { opacity: .5; }
}
.esqueleto {
  min-height: var(--e3);
  border-radius: var(--r-menudo);
  background: linear-gradient(100deg, var(--esq-barra) 0%, var(--esq-brillo) 45%, var(--esq-barra) 80%);
  animation: esqueleto-latido 1.8s ease-in-out infinite;
}
.esqueleto.bloque { min-height: var(--e7); border-radius: var(--r-tarjeta); }


