/* ============================================================
   PORTADA VIVA — la capa que ven los invitados
   ============================================================

   Va arriba del todo de la invitación. NO tapa nada: ocupa su alto y lo de
   debajo sigue exactamente donde estaba.

   LAS MEDIDAS, que es lo que hay que hacer bien:

   El lienzo del motor mide 375 de ancho y se escala con `transform: scale()`
   al ancho real del contenedor — igual que toda la invitación (invariante A20:
   layout a 375, escalado sin tope; A20 fija el ANCHO, del alto no dice nada).

   ⚠️ EL ALTO NO ES 600 PARA TODAS: cada plantilla le da a su portada uno
   distinto (400 en Olive y en la mayoría, 362 en Nos casamos, 600 solo en
   Elegance/Duna/Dolce Vita). Llega del servidor en `--pv-ratio` y
   `data-pv-alto`, medido sobre el póster del montaje. Así el vídeo y su caja
   tienen la MISMA proporción y el `cover` no recorta nada: ni barras negras ni
   recortes raros.

   Sin JS todavía (los primeros milisegundos), el `aspect-ratio` mantiene el
   hueco y el póster lo llena: nunca se ve un salto ni un agujero.

   Y no lleva márgenes de adorno. El degradado inferior del propio motor funde
   la portada con el color de papel de esa invitación, así que un margen aquí
   rompería justamente lo que ese degradado consigue.
   ============================================================ */

.pv-capa {
    position: relative;
    width: 100%;
    /* El ancho de la invitación, no el de la pantalla: en escritorio la
       invitación vive en su columna y la portada tiene que ir con ella. */
    margin: 0;
    padding: 0;
    overflow: hidden;
    background: transparent;
    /* Por encima de las decoraciones de la sección de abajo, por debajo de
       cualquier modal del editor. */
    z-index: 1;
}

/* ============================================================
   LA CAPA OCUPA EL SITIO DE LA FOTO DE PORTADA (01-sep-2026)
   ============================================================

   Hasta esta fecha la capa se emitía arriba del todo, antes de la primera
   sección, y la foto de portada de verdad seguía existiendo unos cientos de
   píxeles más abajo: la invitación tenía DOS portadas.

   Hoy vive dentro del `.campo-mask-wrapper` del campo `imagen_cabecera`,
   encima del `.campo-imagen-inner` y con su misma caja exacta.

   ⚠️ SE SUPERPONE, NO SUSTITUYE, y eso es lo que la hace segura:

   - La caja del campo no cambia de sitio ni de tamaño, así que el canon de
     bloques, el snapshot, LayoutBloques, el corte y el autocalibrado (C3)
     siguen viendo exactamente el mismo layout que antes.
   - Apagar el módulo es UNA clase: debajo está la foto de siempre, intacta, y
     vuelve al momento y sin recargar (`_pvPintarCapaEnIframe`, invariante C4).
   - Y como vive dentro del mask-wrapper, hereda la máscara de la plantilla —en
     «Nos casamos», la acuarela— sin una línea extra.

   El wrapper ya es `position: relative` con `width/height: 100%`
   (`_vista_previa_head.blade.php`), así que `inset: 0` es su caja entera.

   El z-index es 2 y no 1: por encima del `.campo-imagen-inner`, y por debajo
   de las burbujas del editor (la de IA va en 22 y la de subir foto en 20). */
.pv-capa--en-campo {
    position: absolute;
    inset: 0;
    z-index: 2;
}
/* Dentro del campo, el hueco lo da la caja: el `aspect-ratio` sobra y encima
   pelearía con ella. */
.pv-capa--en-campo .pv-capa-lienzo {
    aspect-ratio: auto;
    width: 100%;
    height: 100%;
    background: transparent;
}
@supports not (aspect-ratio: 1) {
    .pv-capa--en-campo .pv-capa-lienzo { height: 100%; padding-bottom: 0; }
}

.pv-capa-lienzo {
    position: relative;
    width: 100%;
    /* ⚠️ LA PROPORCIÓN LA MANDA EL VÍDEO, y llega en `--pv-ratio` desde el
       Blade (`PortadaVivaService::lienzoDelMontaje()`, medida sobre el póster).

       Aquí había `375 / 600` fijo. Ese 600 daba por hecho que la portada mide
       600 px de alto, cierto solo para Elegance/Duna: en Olive mide 400. Con el
       vídeo montado a 750×800 y la capa pidiendo 375×600, el `object-fit:
       cover` lo AMPLIABA y se comía un 33 % de su ancho — encima del 30 % que
       ya se había quitado al recortar la foto. El invitado veía menos paisaje
       en el vídeo que en la foto de portada de la propia invitación.

       El 375/600 se queda de reserva: es lo que se usa si el montaje no trae
       póster, y entonces se comporta exactamente como antes.

       Sostiene el hueco hasta que el JS mide y fija el alto exacto: sin esto,
       la página daría un salto al aparecer la portada. */
    aspect-ratio: var(--pv-ratio, 375 / 600);
    background: #EFEAE1;
    display: block;
}

/* Navegadores sin aspect-ratio: el alto sale del padding. Por defecto,
   600/375 = 160 %. */
@supports not (aspect-ratio: 1) {
    .pv-capa-lienzo { height: 0; padding-bottom: var(--pv-padding, 160%); }
}

/* El póster mientras el motor monta. Es el primer fotograma del vídeo, así que
   el relevo no da ningún salto. */
.pv-capa-poster {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

/* APAGADA. Solo puede darse en el EDITOR: en la publica una portada apagada no
   se emite siquiera. La capa se queda en el DOM para que el interruptor del
   modal la encienda y la apague al momento, sin recargar el iframe.

   `!important` porque `.pv-capa` trae su propio `display`, y sin el la regla
   de mas abajo en el fichero ganaria. */
.pv-capa--apagada { display: none !important; }

/* SIN VIDEO. Tambien solo en el EDITOR, y por el mismo motivo que la de
   arriba: la capa tiene que EXISTIR para poder montar en ella el video en
   cuanto aparezca —al terminar una generacion, o al recuperar una portada
   guardada—, y sin ella eso obligaria a recargar (invariante C4).

   Es una clase APARTE de  porque son dos preguntas distintas:
   «¿lo tienen encendido?» y «¿hay algo que enseñar?». Cada una tiene su dueño
   (el interruptor del modal y el video vigente) y compartir una clase haria
   que se pisaran. */
.pv-capa--sin-video { display: none !important; }

/* ⚠️ PERO LA BURBUJA DE CONFIGURAR NO SE VA CON ELLA (02-sep-2026).
   ============================================================

   Fallo que vio Jose mientras se generaba un video: el panel decia «Portada
   Viva» con su marca de elegida, y sobre la foto de la preview no habia NADA
   que tocar. El unico camino al configurador era el enlace «Configurar» del
   pie del panel.

   La causa es que la burbuja vive DENTRO de la capa, y el `display: none` de
   arriba se la llevaba por delante. Y a la vez `campo-imagen-widget--pv-on`
   —que NO depende del video, solo del interruptor— ya habia quitado la burbuja
   de subir/borrar y cortado el toque en la imagen. Resultado: la portada
   anunciada como activa y sin una sola puerta de entrada encima.

   No es solo mientras se genera. Es TODO estado encendido-sin-video: antes de
   la primera generacion, despues de cambiar la foto (que invalida el montaje)
   y mientras se monta el nuevo.

   Asi que se separa lo que nunca debio ir junto: sin video se esconde EL
   LIENZO —que es lo que no tiene nada que enseñar— y la capa se queda, vacia y
   transparente, sosteniendo su burbuja.

   Tres cautelas:
   - Va atada a `--pv-on`, el MISMO interruptor que gobierna las otras dos
     burbujas del widget: si la Portada Viva no manda en esta foto (la han
     borrado), el campo vuelve a ser un campo y aqui no pinta nada.
   - `:not(.pv-capa--apagada)` porque apagada gana siempre: «solo la foto» no
     deja rastro encima de la foto. Sin el, esta regla —mas especifica— le
     ganaria a la de apagada aunque las dos lleven `!important`.
   - El lienzo se esconde EXPRESAMENTE y no basta con que este vacio:
     `desmontar()` repone dentro `.pv-capa-poster` con la imagen quieta
     (portada-viva-capa.js:188), y esa imagen encima de la foto de portada de
     verdad es justo la mezcla que se corrigio el 02-sep. */
.campo-imagen-widget--pv-on .pv-capa--en-campo.pv-capa--sin-video:not(.pv-capa--apagada) {
    display: block !important;
    /* Vacia no intercepta nada: debajo esta la foto de siempre. */
    pointer-events: none;
}
.campo-imagen-widget--pv-on .pv-capa--en-campo.pv-capa--sin-video:not(.pv-capa--apagada) .pv-capa-lienzo {
    display: none !important;
}
.campo-imagen-widget--pv-on .pv-capa--en-campo.pv-capa--sin-video:not(.pv-capa--apagada) .pv-config-bubble {
    pointer-events: auto;
}

/* Burbuja "Configurar Portada Viva" (SOLO editor): abre el modal.

   ES EL MISMO BOTON QUE EL DE GALERIA Y DRESS CODE, y a proposito: son tres
   modulos con el mismo gesto, asi que se ven igual. Copiado de
   `.dresscode-config-bubble` (public/css/dresscode.css).

   ⚠️ LOS `!important` NO SOBRAN. Las plantillas traen estilos globales de
   <button> —fondo verde, uppercase y `width: 100%`— y sin ellos la burbuja sale
   estirada de lado a lado, que es justo como estaba. `width: fit-content` es la
   linea que la encoge a su texto; `margin: auto` la centra.

   Y va EN FLUJO, no `absolute`: ocupa su sitio arriba y empuja la portada hacia
   abajo, en vez de flotar encima del video tapandolo. */
.pv-config-bubble {
    display: flex !important;
    width: fit-content !important;
    max-width: calc(100% - 24px);
    margin: 4px auto 14px !important;
    align-items: center;
    justify-content: center;
    gap: 7px;
    padding: 8px 16px !important;
    border: none !important;
    border-radius: 999px !important;
    background: linear-gradient(135deg, #334155 0%, #475569 100%) !important;
    color: #fff !important;
    font-size: 13px !important;
    font-weight: 700 !important;
    line-height: 1 !important;
    white-space: nowrap;
    text-transform: none !important;
    letter-spacing: normal !important;
    box-shadow: 0 6px 16px -6px rgba(51, 65, 85, 0.55);
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
}
.pv-config-bubble i { font-size: 12px; }

/* Dentro del campo NO puede ir en flujo: la caja es fija y no hay nada que
   empujar. Va absoluta, arriba, y SE APARTA cuando hace falta.

   SU SITIO DEPENDE DE SI TIENE COMPAÑÍA (regla de Jose, 01-sep-2026):

   - Sola, está CENTRADA. Es lo natural: una burbuja pegada a la derecha con
     medio ancho vacío al lado se ve descolocada.
   - Cuando aparece la píldora de IA —que vive en la esquina izquierda— se
     desplaza a la derecha y comparten la franja, una en cada punta. Se
     desplaza, no salta: con la Portada Viva encendida la de IA solo sale al
     descansar, así que las dos cosas ocurren a la vez y el movimiento
     acompaña a su aparición.

   Y SE VE SIEMPRE, esté la portada reproduciéndose o descansando: es la burbuja
   de la PORTADA, no la de la foto. Las otras dos —IA y subir foto— son de la
   foto, y esas sí esperan al descanso. */
.pv-capa--en-campo .pv-config-bubble {
    position: absolute;
    top: 10px;
    left: 50%;
    transform: translateX(-50%);
    margin: 0 !important;
    z-index: 23;

    /* El tope que impide que las dos se toquen NUNCA, en ningún idioma ni en
       ningún ancho de campo: 10 del hueco izquierdo + ~70 de la píldora de IA
       + 16 de aire. El texto más largo de los 9 idiomas («Skonfiguruj Portada
       Viva», 24 caracteres) mide ~219 px y en un lienzo de 375 le sobran 60,
       así que esto solo entra en juego en campos estrechos — y ahí recorta con
       puntos suspensivos en vez de invadir la esquina. El nombre completo no se
       pierde: el `aria-label` del botón lo lleva entero.

       Es el MISMO en los dos sitios a propósito: si el ancho cambiara a la vez
       que la posición, el texto entraría y saldría de los puntos suspensivos
       durante el viaje. Se mueve, no se encoge. */
    max-width: calc(100% - 96px);
    overflow: hidden;

    /* El viaje. `left` en porcentaje y `transform` son las dos animables, y por
       eso el centrado NO se deshace con `left:auto; right:10px` —de `50%` a
       `auto` no hay transición posible, sería un salto—. La duración y el
       retardo son los de la píldora de IA (0,45 s y 0,15 s en el bloque de más
       abajo): salen juntas o se nota que una llega tarde. */
    transition: left .45s cubic-bezier(.22, .61, .36, 1) .15s,
                transform .45s cubic-bezier(.22, .61, .36, 1) .15s;
}
.pv-capa--en-campo .pv-config-bubble span {
    overflow: hidden;
    text-overflow: ellipsis;
}

/* SE APARTA A LA DERECHA — solo cuando la píldora de IA está DE VERDAD en
   pantalla, que son tres condiciones a la vez:

     1. la píldora existe en el DOM (`:has(.ia-bubble)`) — el blade solo la pinta
        si la IA está activa globalmente y la invitación es posterior a
        `ia_fecha_implementacion`;
     2. el campo tiene foto del usuario — con `data-tiene-imagen="false"` la
        oculta `_css_variables.blade.php` con un `display:none`, y una burbuja
        que no se ve no puede estorbar;
     3. la portada DESCANSA — mientras se reproduce, la de IA está en
        `opacity:0` (bloque de abajo) y esta se queda en el centro.

   Se descarta la burbuja del WOW, que comparte la clase `.ia-bubble` pero vive
   en el campo `datos_importantes`, no en el de la foto.

   Si un navegador no soporta `:has()` (anterior a Chrome 105 / Safari 15.4) la
   regla entera se ignora: la burbuja se queda centrada. Es un ADORNO, y por eso
   puede depender de `:has()`; las prohibiciones de abajo no.

   Solo en el DESCANSO (02-sep-2026, segunda vuelta). Antes también se apartaba
   en el estado encendido-sin-vídeo, porque ahí la píldora de IA se veía; desde
   que la píldora espera al descanso en todos los estados del módulo, sin vídeo
   está sola en la franja y se queda en el centro. */
.campo-imagen-widget--pv-on.campo-imagen-widget--pv-descansa:not([data-tiene-imagen="false"]):has(.ia-bubble:not([data-wow-bubble]))
    .pv-capa--en-campo .pv-config-bubble {
    left: calc(100% - 10px);
    transform: translateX(-100%);
}

/* ============================================================
   LA FOTO YA NO SE TOCA DESDE AQUÍ (solo editor)
   ============================================================

   Son DOS burbujas con la MISMA llave (`campo-imagen-widget--pv-on`) y dos
   reglas distintas, porque no son la misma decisión.

   LA LLAVE: «la Portada Viva MANDA en esta foto». La emite el Blade
   (`_campo_colocado.blade.php`, `PortadaVivaService::mandaEnLaFoto()`) y la
   conmuta en vivo `_pvPintarCapaEnIframe()` (`pvMandaEnLaFoto()` en el mixin).
   Y manda desde que el módulo está EN MARCHA: encendido, O con una generación
   en curso (02-sep-2026, segunda vuelta). Lo segundo no es un matiz: la
   primera generación de una pareja corre con el interruptor todavía apagado
   —lo enciende `completar()` al terminar—, y durante esos minutos la papelera
   y la píldora de IA seguían encima de una foto que ya era la Entrada de un
   trabajo pagado. En móvil se veía; en PC la papelera está apagada por otra
   regla (`.desktop-editor-mode .config-bubble`) y no había dónde verlo.

   Va por una CLASE y NO por `:has(.pv-capa…)`: la capa solo existe cuando ya
   hay vídeo montado, y esta prohibición vale antes. Y una prohibición no
   puede depender de que el navegador soporte un selector.

   1) SUBIR / BORRAR LA FOTO — no vuelve nunca mientras el módulo mande.

      Regla de Jose (02-sep-2026). Con la Portada Viva en marcha, esta foto
      deja de ser un campo del formulario: es la Entrada, y cambiarla cambia el
      vídeo que se va a generar y la escena que se recomienda. Se cambia en un
      solo sitio —el paso 1 del configurador—, y por eso aquí no hay ni botón
      ni toque en la imagen. Que no haya dos caminos es justamente lo que hace
      evidente que la portada está viva.

      Antes esta burbuja volvía al descansar el ciclo, junto con la de IA. En
      móvil eso salía peor de lo que parece: al cargar no estaba, y aparecía
      sola a los veinte segundos, cuando el vídeo terminaba. Una papelera roja
      que se materializa encima de la portada sin que nadie la haya llamado.

   2) LA BURBUJA DE IA — solo en el DESCANSO.

      Esa sí es de la foto, y con el ciclo terminado lo que hay debajo vuelve a
      ser la fotografía: retocarla tiene el mismo sentido de siempre. Pero en
      cualquier otro estado del módulo —reproduciéndose, generándose, o
      encendido sin vídeo— no se ve. Estaba atada a «hay ciclo»
      (`:has(.pv-capa…:not(--sin-video))`) y eso dejaba un agujero del tamaño
      de la generación entera: sin vídeo no hay ciclo, y la píldora se quedaba
      puesta mientras la barra avanzaba. Jose: «esto sólo debe aparecer al
      final como está en el PC». Una burbuja que espera al final se esconde
      por defecto y se enseña EN el final, no al revés.

      Con el módulo apagado (sin `--pv-on`) ninguna de estas reglas existe y la
      píldora se comporta como en cualquier otra invitación.

   Se apagan con `opacity`/`visibility` y NO con `display`, porque las reglas de
   `_css_variables.blade.php` clavan `display: flex !important` en `.ia-bubble`
   y porque un `display` no se puede fundir. */

/* 1) Subir / borrar: fuera mientras la Portada Viva mande. */
.campo-imagen-widget--pv-on .campo-imagen-bubble {
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
}

/* Y la foto deja de anunciarse como pulsable: `_vista_previa_scripts` le clava
   `cursor: pointer` a todos los widgets de imagen, y un cursor de mano sobre
   algo que ya no hace nada es una promesa falsa. Los controles de la portada
   —«Ver de nuevo», «Configurar»— llevan el suyo dentro.

   La clase va DOBLADA a propósito: ese `cursor: pointer` vive en un <style>
   al final del <body> y este fichero es un <link> del <head>, así que con la
   misma especificidad ganaría él por ir después. */
.campo-imagen-widget.campo-imagen-widget--pv-on { cursor: default; }

/* 2) IA: fuera mientras la Portada Viva mande, y vuelve SOLO con el descanso.

   `--pv-descansa` la pone y la quita `portada-viva-capa.js` desde los
   callbacks del motor (`alDescansar` / `alDespertar`), y `desmontar()` la
   quita al retirar el vídeo: sin ciclo no hay descanso, así que generando o
   sin vídeo la píldora no aparece. Las dos clases van en el MISMO widget, que
   es el único padre común de la capa (shadow root) y de las burbujas. */
.campo-imagen-widget--pv-on .ia-bubble {
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transition: opacity .45s ease, visibility .45s;
}
.campo-imagen-widget--pv-on.campo-imagen-widget--pv-descansa .ia-bubble {
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
    transition-delay: .15s;
}

/* El contenido del shadow root ocupa todo el hueco. */
.pv-capa-lienzo > .pv-lienzo {
    position: absolute;
    top: 0;
    left: 0;
}

/* ============================================================
   LA BANDA DE «SE ESTÁ HACIENDO» (03-sep-2026) — SOLO EDITOR
   ============================================================

   Petición de Jose: el progreso vivía solo dentro del configurador y, al
   cerrarlo, la pantalla se quedaba muda. La portada seguía siendo la foto de
   siempre y no había forma de saber si el encargo se había perdido, si había
   fallado o si simplemente aún no había terminado.

   Ahora la señal está donde mira la pareja: pegada al borde inferior de su
   propia portada, en el editor de PC y en el de móvil, en cualquier plantilla.

   TRES COSAS QUE NO SE TOCAN:

   1. NO MUEVE NADA. `position: absolute` dentro de la caja del campo de la
      foto — la caja no cambia de sitio ni de tamaño, así que el canon de
      bloques, el snapshot, `LayoutBloques`, el corte y el autocalibrado (C3)
      siguen midiendo exactamente lo mismo que antes.

   2. NUNCA POR ENCIMA DE UN TEXTO. Y no por un z-index afinado, sino por
      construcción: cada `.campo-texto-colocado` es `position:absolute` con su
      propio `z-index`, o sea que crea su contexto de apilamiento. Nada de lo
      que viva dentro del widget de la imagen puede pasar por delante de un
      campo de texto con z-index mayor — que es justo el caso de las portadas
      cuyo texto llega hasta abajo del todo. El 19 de aquí dentro solo la
      ordena respecto a las burbujas del editor (20 y 23), que sí mandan.

   3. NO INTERCEPTA UN SOLO CLIC. `pointer-events: none` en todo el árbol.

   Y el ancho es el de la caja del campo, así que la escala del layout a 375 px
   (A20) se le aplica igual que a todo lo demás: en el móvil se ve exactamente
   proporcionada, sin una media query. */
.pv-progreso {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 19;
    pointer-events: none;
    /* Empieza fuera. La entrada y la salida son la MISMA transición al revés:
       la banda no aparece de golpe ni desaparece de golpe, porque los dos
       momentos ocurren mientras la pareja está mirando otra cosa. */
    opacity: 0;
    visibility: hidden;
    transform: translateY(6px);
    transition: opacity .45s ease, visibility .45s, transform .45s cubic-bezier(.22, .61, .36, 1);
}
.pv-progreso--on {
    opacity: 1;
    visibility: visible;
    transform: none;
}
.pv-progreso * { pointer-events: none; }

/* EL VELO. Es lo único que hace legible el texto sobre CUALQUIER foto —una
   playa a mediodía o una cena a oscuras— sin pintar una banda opaca encima de
   la portada. Se funde a nada hacia arriba, así que no traza ningún borde.

   El degradado va en tres paradas y no en dos a propósito: con dos, el corte
   se ve como una línea recta sobre las fotos claras. */
.pv-progreso-velo {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    height: 74px;
    display: block;
    background: linear-gradient(to top,
        rgba(15, 18, 24, 0.62) 0%,
        rgba(15, 18, 24, 0.34) 42%,
        rgba(15, 18, 24, 0) 100%);
}

/* EL TEXTO. Dos líneas: qué está pasando y cuánto queda.

   Los `!important` no sobran: las plantillas traen estilos globales para
   `span`, y el hub de tipografía le pondría a esto la caligráfica de la pareja
   —con la que un aviso de estado no se lee—. La familia la resuelve
   `hub_css_chrome()` porque la clase está registrada en `hub_clases_chrome()`
   (A18); el tamaño, la altura de línea y el `text-transform` los clava aquí. */
.pv-progreso-texto {
    position: relative;
    display: block;
    padding: 0 12px 6px;
    text-align: center !important;
    text-transform: none !important;
    letter-spacing: 0 !important;
    color: #fff !important;
    /* La sombra es la segunda red del velo: sobre un blanco puro el velo solo
       ya deja el texto justo, y esto lo asienta sin oscurecer la foto. */
    text-shadow: 0 1px 3px rgba(0, 0, 0, 0.55);
}
.pv-progreso-titulo {
    display: block;
    font-size: 11px !important;
    line-height: 1.25 !important;
    font-weight: 700 !important;
    font-style: normal !important;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.pv-progreso-resta {
    display: block;
    margin-top: 1px;
    font-size: 10px !important;
    line-height: 1.25 !important;
    font-weight: 400 !important;
    font-style: normal !important;
    opacity: .82;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
/* Ningún hijo hereda una familia de la plantilla: la inline de `.pv-progreso-texto`
   manda en todo el árbol. Las plantillas traen reglas globales para `b` y `span`. */
.pv-progreso-texto span,
.pv-progreso-texto b { font-family: inherit !important; }

/* EL PORCENTAJE (petición de Jose, 02-sep-2026): abre la segunda línea, en
   negrita, y el separador « · » solo existe si hay número — en cola no lo hay,
   y «· Preparando…» con un punto huérfano delante se leería como un error.
   Es el mismo número que el del configurador: los dos salen de
   `portadaViva.progreso`. */
.pv-progreso-pct {
    font-weight: 700 !important;
    font-style: normal !important;
    font-size: inherit !important;
    color: inherit !important;
}
.pv-progreso-pct:not(:empty)::after {
    content: '\00a0\00b7\00a0';
    font-weight: 400 !important;
}

/* El punto que late. Es la señal de vida que se lee sin leer: dice «esto sigue
   pasando» aunque el texto no se mire. */
.pv-progreso-punto {
    display: inline-block;
    width: 5px;
    height: 5px;
    margin-right: 6px;
    vertical-align: middle;
    border-radius: 50%;
    background: #fff;
    box-shadow: 0 0 0 0 rgba(255, 255, 255, 0.6);
    animation: pv-progreso-latido 1.8s ease-in-out infinite;
}
@keyframes pv-progreso-latido {
    0%, 100% { opacity: 1;  box-shadow: 0 0 0 0 rgba(255, 255, 255, 0.55); }
    50%      { opacity: .45; box-shadow: 0 0 0 4px rgba(255, 255, 255, 0); }
}

/* LA BARRA SE LLENA (petición de Jose, 02-sep-2026). Nació indeterminada y sin
   número —«el porcentaje vive en el configurador»— y Jose, con una portada
   generándose delante, pidió el porcentaje aquí. Es el MISMO que el del
   configurador: `portadaViva.progreso`, contra la espera real medida, y se
   para en el 95 % hasta que el vídeo existe. El porcentaje nunca manda sobre
   el hecho.

   Lo que se conserva de la barra indeterminada es el destello, que ahora
   recorre solo la parte llena: es la señal de vida que sigue diciendo «esto
   sigue pasando» cuando el número lleva un rato en 95.

   3 px es el grosor de un indicador de carga de navegador: se ve, y no es un
   elemento de la portada. */
.pv-progreso-barra {
    position: relative;
    display: block;
    height: 3px;
    overflow: hidden;
    background: rgba(255, 255, 255, 0.28);
}
.pv-progreso-lleno {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    width: 0;
    display: block;
    overflow: hidden;
    border-radius: 0 3px 3px 0;
    background: rgba(255, 255, 255, 0.78);
    /* El ancho lo escribe el reloj del mixin cada segundo; la transición hace
       que entre dos escrituras se deslice en vez de saltar. */
    transition: width 1s linear;
}
.pv-progreso-pulso {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    width: 38%;
    display: block;
    background: linear-gradient(90deg,
        rgba(255, 255, 255, 0) 0%,
        rgba(255, 255, 255, 1) 50%,
        rgba(255, 255, 255, 0) 100%);
    animation: pv-progreso-desliza 1.9s cubic-bezier(.45, 0, .55, 1) infinite;
}
@keyframes pv-progreso-desliza {
    0%   { transform: translateX(-105%); }
    100% { transform: translateX(370%); }
}

/* Quien pide menos movimiento no recibe nada que se mueva. El aviso no
   desaparece: se queda la banda con su texto y la barra quieta, que es lo que
   de verdad informa. */
@media (prefers-reduced-motion: reduce) {
    .pv-progreso { transition: opacity .2s ease, visibility .2s; transform: none; }
    .pv-progreso-punto { animation: none; }
    .pv-progreso-lleno { transition: none; }
    .pv-progreso-pulso { animation: none; display: none; }
}
