.liveness-container {
    position: relative;
    width: 100%;
    /* Sin max-width: el container ocupa todo el ancho del padre (ideal para
       que el video llene el formulario en desktop y móvil). El ancho final
       lo decide el contenedor padre del componente que invoca al módulo. */
    margin: 0 auto;
    font-family: inherit;
}

.liveness-stage {
    position: relative;
    width: 100%;
    /* aspect-ratio se setea dinámicamente en JS al cargar el video para que
       coincida con la cámara (video.videoWidth/videoHeight). Así no hay
       letterbox ni crop y los landmarks normalizados de MediaPipe alinean
       directamente con las coordenadas CSS del óvalo.
       El 3/4 es solo default antes de que cargue la cámara. */
    aspect-ratio: 3 / 4;
    max-height: 70vh;
    background: #000;
    border-radius: 16px;
    overflow: hidden;
}

.liveness-video {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    /* cover: como el container ahora adopta el aspect de la cámara, cover
       no recorta nada (aspect idéntico). Si la aspect-ratio dinámica fallara
       por algún motivo, cover evita barras negras feas y solo recorta un poco. */
    object-fit: cover;
    transform: scaleX(-1);
}

.liveness-canvas {
    display: none;
}

.liveness-oval {
    position: absolute;
    /* Centrado horizontal+vertical en el stage. Forma fija "vertical de cara"
       (aspect 10/13 ~ 0.77) independiente del aspect del stage (que cambia
       según la cámara: landscape desktop 16:9, portrait móvil 9:16, etc).
       max-width / max-height previenen overflow en cámaras extremas.
       La validación de "face in position" del backend (facelink->position()
       width=0.55, height=0.60) se sigue cumpliendo si el usuario entra al
       óvalo razonablemente — el auto-crop alinea exacto al target. */
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
    height: 80%;
    aspect-ratio: 10 / 13;
    max-width: 70%;
    pointer-events: none;
}

.liveness-oval-svg {
    width: 100%;
    height: 100%;
}

/* Con pathLength=100 en la <ellipse> y vector-effect=non-scaling-stroke,
   el dasharray queda normalizado (ptnos por 0..100 del perímetro real) y el
   grosor del stroke no se deforma al estirar el óvalo en aspect 10/13. */
.liveness-oval-bg {
    fill: none;
    stroke: rgba(255, 255, 255, 0.75);
    stroke-width: 3;
    stroke-linecap: round;
    stroke-dasharray: 2 2;
    transition: stroke 0.25s ease, stroke-width 0.25s ease, filter 0.25s ease;
}

/* Mientras inicializa, ocultamos el óvalo para que no se vea raro sobre el
   video negro antes de que arranque la cámara. El spinner es el indicador. */
.liveness-stage[data-state="initializing"] .liveness-oval,
.liveness-stage[data-state="waiting_permission"] .liveness-oval { opacity: 0; }
.liveness-oval { transition: opacity 0.3s ease; }

.liveness-oval[data-state="initializing"] .liveness-oval-bg { animation: liveness-pulse 1.6s ease-in-out infinite; }

/* Spinner centrado con label "Preparando…" durante INITIALIZING / WAITING.
   Pulido estilo iOS: anillo que gira + texto debajo en card glassmorphism. */
.liveness-spinner {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 14px;
    padding: 20px 28px;
    background: rgba(0, 0, 0, 0.55);
    backdrop-filter: blur(8px);
    -webkit-backdrop-filter: blur(8px);
    border-radius: 14px;
    pointer-events: none;
    opacity: 0;
    transition: opacity 0.3s ease;
    z-index: 6;
}

.liveness-stage[data-state="initializing"] .liveness-spinner,
.liveness-stage[data-state="waiting_permission"] .liveness-spinner {
    opacity: 1;
}

.liveness-spinner-ring {
    width: 44px;
    height: 44px;
    border-radius: 50%;
    border: 3px solid rgba(255, 255, 255, 0.2);
    border-top-color: #fff;
    animation: liveness-spin 900ms linear infinite;
}

.liveness-spinner-label {
    color: #fff;
    font-size: 14px;
    font-weight: 500;
    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
    white-space: nowrap;
}

@keyframes liveness-spin {
    to { transform: rotate(360deg); }
}
/* Colores del trazo punteado por estado. El mismo <ellipse> cambia de color
   y grosor según el estado; no hay stroke sólido adicional. */
.liveness-oval[data-state="waiting_permission"] .liveness-oval-bg { stroke: rgba(255, 255, 255, 0.6); }
.liveness-oval[data-state="no_face"] .liveness-oval-bg        { stroke: #e63946; stroke-width: 3.5; }
.liveness-oval[data-state="multiple_faces"] .liveness-oval-bg { stroke: #e63946; stroke-width: 3.5; }
.liveness-oval[data-state="out_of_bounds"] .liveness-oval-bg  { stroke: #f4a261; stroke-width: 3.5; }
.liveness-oval[data-state="in_position"] .liveness-oval-bg {
    stroke: #ffd60a;
    stroke-width: 4;
    filter: drop-shadow(0 0 4px rgba(255, 214, 10, 0.5));
    animation: liveness-breathe 1.4s ease-in-out infinite;
}
.liveness-oval[data-state="challenge_running"] .liveness-oval-bg {
    stroke: #2a9d8f;
    stroke-width: 4;
    filter: drop-shadow(0 0 6px rgba(42, 157, 143, 0.6));
    animation: liveness-breathe 1.8s ease-in-out infinite;
}
.liveness-oval[data-state="capturing"] .liveness-oval-bg {
    stroke: #2a9d8f;
    stroke-width: 5;
    filter: drop-shadow(0 0 10px rgba(42, 157, 143, 0.85));
}
.liveness-oval[data-state="success"] .liveness-oval-bg {
    stroke: #2a9d8f;
    stroke-width: 5;
    filter: drop-shadow(0 0 12px rgba(42, 157, 143, 0.9));
    animation: liveness-success-pulse 1.2s ease-out;
}
.liveness-oval[data-state="failed"] .liveness-oval-bg { stroke: #e63946; stroke-width: 4; }

@keyframes liveness-breathe {
    0%, 100% { opacity: 0.85; }
    50%      { opacity: 1; }
}

@keyframes liveness-success-pulse {
    0%   { filter: drop-shadow(0 0 4px rgba(42, 157, 143, 0.5)); }
    40%  { filter: drop-shadow(0 0 18px rgba(42, 157, 143, 1)); }
    100% { filter: drop-shadow(0 0 12px rgba(42, 157, 143, 0.9)); }
}

@keyframes liveness-pulse {
    0%, 100% { opacity: 0.4; }
    50% { opacity: 1; }
}

.liveness-countdown {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: 140px;
    height: 140px;
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.95);
    color: #2a9d8f;
    font-size: 90px;
    font-weight: 700;
    line-height: 140px;
    text-align: center;
    opacity: 0;
    pointer-events: none;
    box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
    z-index: 10;
}

.liveness-countdown[data-active="true"] {
    animation: liveness-countdown 800ms ease-out;
}

@keyframes liveness-countdown {
    0%   { opacity: 0; transform: translate(-50%, -50%) scale(0.6); }
    20%  { opacity: 1; transform: translate(-50%, -50%) scale(1.1); }
    40%  { transform: translate(-50%, -50%) scale(1); }
    90%  { opacity: 1; }
    100% { opacity: 0; transform: translate(-50%, -50%) scale(0.8); }
}

.liveness-flash {
    position: absolute;
    inset: 0;
    background: #fff;
    opacity: 0;
    pointer-events: none;
}

.liveness-flash[data-active="true"] {
    animation: liveness-flash 0.4s ease-out;
}

@keyframes liveness-flash {
    0% { opacity: 0; }
    20% { opacity: 0.8; }
    100% { opacity: 0; }
}

.liveness-instruction {
    /* Overlay al pie del stage para que siempre sea visible (incluso si el
       video ocupa todo el viewport). Bloque translúcido oscuro que se
       superpone sin tapar el rostro del usuario. */
    position: absolute;
    left: 12px;
    right: 12px;
    bottom: 12px;
    text-align: center;
    font-size: 17px;
    font-weight: 600;
    color: #fff;
    min-height: 28px;
    padding: 10px 16px;
    background: rgba(0, 0, 0, 0.6);
    border-radius: 10px;
    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
    backdrop-filter: blur(4px);
    -webkit-backdrop-filter: blur(4px);
    z-index: 5;
}

.liveness-progress {
    margin-top: 12px;
    text-align: center;
    display: none;
}

.liveness-container[data-show-progress="true"] .liveness-progress {
    display: block;
}

.liveness-progress-dot {
    display: inline-block;
    width: 10px;
    height: 10px;
    border-radius: 50%;
    background: #ddd;
    margin: 0 4px;
}

.liveness-progress-dot[data-completed="true"] {
    background: #2a9d8f;
}

.liveness-actions {
    margin-top: 16px;
    text-align: center;
}

.liveness-actions[hidden] {
    display: none;
}

.liveness-scroll-lock {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.4);
    z-index: 1040;
    pointer-events: auto;
}

/* Modal móvil estilo "card centrado" (no fullscreen): backdrop oscuro
   semitransparente que deja entrever la página detrás + dialog centrado
   con padding, fondo oscuro y bordes redondeados. En desktop NO se aplica
   (el container queda inline donde lo puso el caller). Activación vía JS:
   isMobileDevice() en liveness.js controla _enterModal/_exitModal. */
.liveness-modal-overlay {
    position: fixed;
    inset: 0;
    z-index: 10000;                            /* sobre .liveness-scroll-lock (1040) */
    background: rgba(0, 0, 0, 0.72);           /* backdrop semi-translúcido */
    display: flex;
    align-items: center;
    justify-content: center;
    /* Safe-area + breathing room: iPhones con notch/dynamic island. */
    padding: max(16px, env(safe-area-inset-top))
             max(16px, env(safe-area-inset-right))
             max(16px, env(safe-area-inset-bottom))
             max(16px, env(safe-area-inset-left));
    box-sizing: border-box;
    animation: liveness-modal-in 200ms ease-out;
    -webkit-backdrop-filter: blur(2px);
    backdrop-filter: blur(2px);
}

@keyframes liveness-modal-in {
    from { opacity: 0; }
    to   { opacity: 1; }
}

/* El container actúa como modal-dialog: card centrado con ancho EXPLÍCITO.
   Con width: auto y align-items: center del overlay, el container colapsaba
   al min-content (el video es absolute → 0px), dejando un dialog tipo
   "pildora" de unos pocos px. El width fijo evita ese colapso. */
.liveness-container.liveness-in-modal {
    width: min(92vw, 460px);
    max-width: 100%;
    height: auto;
    max-height: 92vh;
    margin: 0;
    padding: 16px;
    background: #1a1a1a;
    border-radius: 18px;
    box-shadow: 0 24px 60px rgba(0, 0, 0, 0.55);
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 12px;
    box-sizing: border-box;
    animation: liveness-modal-dialog-in 280ms cubic-bezier(0.16, 1, 0.3, 1);
}

@keyframes liveness-modal-dialog-in {
    from { transform: scale(0.92); opacity: 0; }
    to   { transform: scale(1); opacity: 1; }
}

/* Stage dentro del dialog: width 100% (no auto, para que aspect-ratio
   compute height); max-height clampa para dejar espacio a progress + retry. */
.liveness-container.liveness-in-modal .liveness-stage {
    width: 100%;
    max-width: 100%;
    height: auto;
    max-height: calc(92vh - 120px);
    border-radius: 14px;
    flex-shrink: 1;
}

/* Las acciones (botón Reintentar) en modal son más prominentes. */
.liveness-container.liveness-in-modal .liveness-actions {
    margin-top: 0;
}

.liveness-container.liveness-in-modal .liveness-actions .btn {
    padding: 12px 28px;
    font-size: 16px;
    border-radius: 12px;
}

/* Progress dots en modal — fondo oscuro requiere dots con contraste. */
.liveness-container.liveness-in-modal .liveness-progress-dot {
    background: rgba(255, 255, 255, 0.25);
}

.liveness-container.liveness-in-modal .liveness-progress-dot[data-completed="true"] {
    background: #2a9d8f;
}

@media (max-width: 600px) {
    .liveness-instruction {
        font-size: 18px;
    }
}
