/* ============================================================================
   PROSPECTOS · VISOR PÚBLICO

   Mobile primero: todo lo de acá está escrito para una pantalla de teléfono
   sostenida con una mano, y las consultas de medio agregan lo que sobra en una
   pantalla grande. No al revés.

   Los valores de color, radio, sombra, foco y tipografía salen de
   andromaco-tokens.css (los --ui-*). Acá no se escribe un hex suelto: si hace
   falta un color que no está en los tokens, se deriva de uno que sí está.
   ========================================================================= */

/* -- Alias locales -----------------------------------------------------------
   Las medidas propias de esta aplicación. Densidad y escala tipográfica no
   vienen del estándar compartido: cada app define la suya, y la de un visor de
   lectura es más generosa que la de una grilla de gestión.                    */
:root {
    --v-barra-alto: 3.75rem;
    --v-espacio-xs: .25rem;
    --v-espacio-sm: .5rem;
    --v-espacio-md: .75rem;
    --v-espacio-lg: 1.25rem;
    --v-espacio-xl: 2rem;

    /* Fondo del área de lectura: más oscuro que la página para que la hoja
       blanca del PDF se despegue. En tema oscuro se invierte la relación. */
    --v-fondo-lectura: #e4e9f0;

    /* El papel del PDF. No es un color de marca y no sale de los tokens: una
       hoja es blanca en los dos temas, porque lo que se está mostrando es un
       documento impreso, no una superficie de la aplicación. */
    --v-papel: #ffffff;

    /* Realce de los botones sobre la barra superior, que tiene degradado: se
       aclara con blanco translúcido en vez de con un color fijo, así funciona
       sobre cualquier punto del degradado. */
    --v-realce-barra: rgb(255 255 255 / .14);
    --v-realce-barra-fuerte: rgb(255 255 255 / .22);

    /* Selección de texto: el mismo azul del foco del estándar, translúcido. */
    --v-seleccion: rgb(21 147 227 / .35);

    /* Resaltado de búsqueda. El activo usa el ámbar de atención del estándar;
       los otros, una versión apagada del mismo matiz. */
    --v-marca: rgba(164, 108, 0, .28);
    --v-marca-activa: rgba(164, 108, 0, .65);

    /* Tintes de la escena del 404.

       El estándar no tiene una rampa de tintes del acento —sólo --ui-accent y
       su -soft, que sobre --ui-bg-page casi no se distingue— así que los tres
       pasos intermedios se declaran acá. Son mezclas de #0864aa (--ui-accent)
       sobre blanco al 9 %, 12 % y 22 %.

       Se escriben como hex y no con color-mix() a propósito: esta página se
       abre escaneando un QR con cualquier teléfono, y en un navegador que no
       conozca la función el custom property queda inválido al sustituirse y el
       relleno cae a negro. Un círculo negro en el medio de la pantalla es un
       precio muy alto por ahorrarse tres constantes. */
    --v-ilus-halo: #d5e6f7;
    --v-ilus-hoja: #9cc5e8;
    --v-ilus-suave: #c6dcf1;
    --v-ilus-renglon: #cfe0f2;
    --v-ilus-sombra: #dbe6f2;
    --v-ilus-borde: #e2eaf4;

    /* Fondo del bloque de ayuda: el mismo azul del halo de la ilustración pero
       aclarado un tono, para que la caja se lea de la misma familia que la
       escena de arriba sin pesar como ella. El borde sí usa el halo tal cual. */
    --v-ayuda-fondo: #e6f0fa;

    /* El rojo de la X. Es --ui-danger aclarado: el token está calibrado para
       texto —tiene que dar 4.5:1 sobre el fondo— y a ese nivel de oscuridad, en
       un trazo grueso rodeado de azules claros, se lee marrón. Acá el rojo no
       lleva ninguna letra encima, así que puede ser el rojo que la escena pide. */
    --v-ilus-x: #d93a41;

    /* Los puntitos decorativos de las esquinas. */
    --v-punteado: rgb(8 100 170 / .16);

    /* El logo de estado__marca es el mismo archivo azul en los dos temas: no
       hay un segundo SVG que swapear sin arriesgar un tamaño distinto (los
       viewBox de logo-andromaco.svg y logo-andromaco-azul.svg no son
       idénticos). brightness(0) lo deja negro sin tocar el alfa e invert(1)
       lo pasa a blanco, así que la silueta y el tamaño quedan exactamente
       iguales. */
    --v-marca-logo-filtro: none;
}

:root[data-tema="dark"],
:root[data-tema="auto"] {
    color-scheme: light dark;
}

@media (prefers-color-scheme: dark) {
    :root[data-tema="auto"] {
        --v-fondo-lectura: #05101b;
        --v-marca: rgba(217, 161, 58, .32);
        --v-marca-activa: rgba(217, 161, 58, .7);

        /* Mismas proporciones, pero mezcladas sobre --ui-bg-surface (#0f2438)
           en vez de sobre blanco, y partiendo del acento claro (#3b9ddd). */
        --v-ilus-halo: #14324b;
        --v-ilus-hoja: #3d7ba8;
        --v-ilus-suave: #24506f;
        --v-ilus-renglon: #1f4664;
        --v-ilus-sombra: #0b1c2c;
        --v-ilus-borde: #24455f;
        --v-ayuda-fondo: #12293f;
        --v-ilus-x: #e8697d;

        --v-punteado: rgb(59 157 221 / .18);

        --v-marca-logo-filtro: brightness(0) invert(1);
    }
}

/* -- Base ------------------------------------------------------------------ */
*, *::before, *::after { box-sizing: border-box; }

html, body {
    height: 100%;
    margin: 0;
}

body {
    font-family: var(--ui-font-family);
    font-size: 1rem;
    line-height: 1.5;
    color: var(--ui-text-primary);
    background: var(--v-fondo-lectura);

    /* El rebote elástico de iOS sobre un documento largo hace que la barra
       flotante se despegue del borde. */
    overscroll-behavior: none;
    -webkit-text-size-adjust: 100%;
}

button, input {
    font: inherit;
    color: inherit;
}

:where(button, input, a, [tabindex]):focus-visible {
    outline: 2px solid var(--ui-focus-color);
    outline-offset: 2px;
    border-radius: var(--ui-radius-xs);
}

.iconos { display: none; }

/* Texto que existe sólo para quien escucha la página: da el contexto que en
   pantalla lo da la posición o el color. No se usa display:none porque eso lo
   sacaría también del árbol de accesibilidad, que es justo lo contrario. */
.solo-lectores {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}

/* Todos los íconos son trazos del mismo grosor: se definen una vez acá y los
   <use> de cada botón heredan. */
svg[viewBox] {
    width: 1.375rem;
    height: 1.375rem;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.75;
    stroke-linecap: round;
    stroke-linejoin: round;
    flex: none;
}

/* ── Barra superior ─────────────────────────────────────────────────────── */
.barra {
    position: fixed;
    inset: 0 0 auto;
    z-index: 20;
    display: flex;
    align-items: center;
    gap: var(--v-espacio-sm);
    min-height: var(--v-barra-alto);
    padding: 0 var(--v-espacio-md);
    padding-top: env(safe-area-inset-top);
    background: var(--ui-topbar-bg);
    color: var(--ui-text-on-fill);
    box-shadow: var(--ui-shadow-sm);
}

/* ── Marca ──────────────────────────────────────────────────────────────────
   Logo, una línea divisoria y el nombre de la sección. Es el patrón de "marca
   + producto" de siempre, y funciona acá por la misma razón de siempre: dice
   de quién es la aplicación sin pedir atención.

   Todo el bloque está calibrado para NO competir con los botones de la
   derecha, que son lo único accionable de la barra. Por eso el nombre de la
   sección va en peso normal y levemente translúcido, y no en negrita: si los
   dos lados gritan, el ojo no sabe dónde ir y termina mirando el que está más
   a la izquierda, que es justamente el que no hace nada.                     */
.marca {
    display: flex;
    align-items: center;
    gap: .625rem;
    flex: 1;
    min-width: 0;
    margin: 0;
    font-size: 1rem;
    font-weight: 400;

    --v-logo-alto: 1.75rem;

    /* Cuánto hay que bajar el texto para que se lea en la misma línea que la
       palabra "Andrómaco" del logo.

       El navegador centra el logo por su caja, y esa caja la define el isotipo
       —el busto con laureles—, que es bastante más alto que el wordmark. Medido
       sobre el SVG: el centro del wordmark cae 4,65 de las 38 unidades del
       viewBox por debajo del centro de la caja, o sea un 12,2 % del alto. Sin
       compensarlo, "Prospecto" flota por encima de "Andrómaco" y las dos
       palabras no se leen como una sola línea.

       Va en proporción al alto del logo y no en píxeles fijos, así que la
       alineación se mantiene sola cuando el logo cambia de tamaño. */
    --v-ajuste-linea: calc(var(--v-logo-alto) * .122);
}

/* El alto se elige mirando el wordmark, no la caja del archivo. Ver el
   comentario del viewBox recortado en img/logo-andromaco.svg. */
.marca__logo {
    height: var(--v-logo-alto);
    width: auto;
    display: block;
    flex: none;
}

/* Un pelo de línea, no un borde. Separa sin dibujar una caja. Acompaña al texto
   en el ajuste para que los tres elementos se lean como un solo bloque. */
.marca__division {
    width: 1px;
    height: 1.625rem;
    flex: none;
    background: currentColor;
    opacity: .3;
    transform: translateY(var(--v-ajuste-linea));
}

.marca__seccion {
    /* Más chico que el cuerpo del texto y con el interletrado abierto: es lo que
       hace que una palabra sola al lado de un logo se lea como un nombre y no
       como un título. Un poco translúcida para que el blanco pleno quede
       reservado a los botones, que son lo accionable. */
    font-size: .9375rem;
    letter-spacing: .02em;
    opacity: .9;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;

    transform: translateY(var(--v-ajuste-linea));
}

/* En un teléfono dice "Prospecto" —que es lo que hay en pantalla, uno— y en
   una pantalla grande se completa a "Prospectos digitales", el nombre del
   sistema. Las dos formas son correctas; entra la que da el ancho. */
.marca__cola { display: none; }

.barra__acciones {
    display: flex;
    align-items: center;
    gap: var(--v-espacio-xs);
}

/* ── Botones ────────────────────────────────────────────────────────────── */
.boton-icono {
    display: inline-flex;
    align-items: center;
    justify-content: center;

    /* 2.75rem = 44px, el mínimo táctil. Debajo de eso, en un teléfono se falla
       el toque y en un prospecto eso significa perder el renglón que se estaba
       leyendo. */
    width: 2.75rem;
    height: 2.75rem;

    padding: 0;
    border: 0;
    border-radius: var(--ui-radius-sm);
    background: transparent;
    color: inherit;
    cursor: pointer;
    transition: background-color var(--ui-transition-fast);
    -webkit-tap-highlight-color: transparent;
}

.boton-icono:hover:not(:disabled) { background: var(--v-realce-barra); }
.boton-icono:active:not(:disabled) { background: var(--v-realce-barra-fuerte); }
.boton-icono[aria-expanded="true"] { background: var(--v-realce-barra-fuerte); }

.boton-icono:disabled {
    opacity: .35;
    cursor: default;
}

.boton-icono--chico {
    width: 2.25rem;
    height: 2.25rem;
}

.boton-icono--chico svg { width: 1.125rem; height: 1.125rem; }

.boton {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 2.75rem;
    padding: 0 var(--v-espacio-lg);
    border: 1px solid transparent;
    border-radius: var(--ui-radius-sm);
    font-weight: 600;
    cursor: pointer;
    transition: background-color var(--ui-transition-fast);
}

.boton--primario {
    background: var(--ui-accent);
    color: var(--ui-text-on-accent);
}

.boton--primario:hover { background: var(--ui-accent-hover); }

/* ── Buscador ───────────────────────────────────────────────────────────── */
.buscador {
    position: absolute;
    inset: 100% 0 auto;
    display: flex;
    align-items: center;
    gap: var(--v-espacio-xs);
    padding: var(--v-espacio-sm) var(--v-espacio-md);
    background: var(--ui-bg-surface);
    color: var(--ui-text-primary);
    border-bottom: 1px solid var(--ui-border);
    box-shadow: var(--ui-shadow-sm);
}

.buscador[hidden] { display: none; }

.buscador__campo {
    position: relative;
    display: flex;
    align-items: center;
    flex: 1;
    min-width: 0;
    gap: var(--v-espacio-sm);
    padding: 0 var(--v-espacio-md);
    border: 1px solid var(--ui-border-strong);
    border-radius: var(--ui-radius-pill);
    background: var(--ui-bg-muted);
}

.buscador__campo:focus-within {
    border-color: var(--ui-focus-color);
    box-shadow: var(--ui-focus-ring);
}

.buscador__lupa {
    width: 1.125rem;
    height: 1.125rem;
    color: var(--ui-text-muted);
}

.buscador__entrada {
    flex: 1;
    min-width: 0;
    height: 2.5rem;
    border: 0;
    background: none;
    outline: none;

    /* Menos de 16px hace que iOS haga zoom al enfocar el campo y descoloque
       todo el documento. */
    font-size: 1rem;
}

.buscador__entrada::-webkit-search-cancel-button { display: none; }

.buscador__conteo {
    font-size: .8125rem;
    color: var(--ui-text-muted);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.buscador .boton-icono:hover:not(:disabled) { background: var(--ui-neutral-soft); }

/* ── Documento ──────────────────────────────────────────────────────────── */
.documento {
    height: 100%;
    padding: calc(var(--v-barra-alto) + env(safe-area-inset-top) + var(--v-espacio-md))
             var(--v-espacio-md)
             calc(5.5rem + env(safe-area-inset-bottom));
    overflow: auto;
    outline: none;

    /* La inercia nativa en iOS; sin esto el scroll de un documento largo se
       siente pegajoso. */
    -webkit-overflow-scrolling: touch;

    /* El gesto de pellizcar lo maneja el JavaScript para poder rehacer el
       dibujo a la escala nueva: si el navegador hiciera su propio zoom, el
       texto quedaría agrandado y borroso. */
    touch-action: pan-x pan-y;
}

.documento__lienzo {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--v-espacio-md);

    /* El origen arriba a la izquierda es lo que hace que el pellizco crezca
       desde donde el dedo está y no desde el centro de la pantalla. */
    transform-origin: 0 0;
    will-change: transform;
}

/* ── Página ─────────────────────────────────────────────────────────────── */
.pagina {
    position: relative;
    background: var(--v-papel);
    box-shadow: var(--ui-shadow-sm);
    border-radius: var(--ui-radius-xs);
    overflow: hidden;

    /* Cuánto tiene que sobrar arriba cuando se salta a esta página. Es el alto
       de la barra fija más un respiro: sin esto, ir a la página 3 la deja con
       el encabezado escondido detrás de la barra. Lo aplica el navegador al
       hacer scrollIntoView, así que el JavaScript no necesita saber cuánto
       mide la barra. */
    scroll-margin-top: calc(var(--v-barra-alto) + env(safe-area-inset-top) + var(--v-espacio-md));

    /* El alto se fija por JavaScript apenas se conocen las dimensiones reales,
       antes de dibujar nada. Así la barra de desplazamiento tiene el largo
       correcto desde el principio y no salta a medida que se van dibujando las
       páginas. */
    flex: none;
}

.pagina__lienzo {
    display: block;
    width: 100%;
    height: 100%;
}

/* Mientras la página todavía no se dibujó se ve el papel en blanco con un
   barrido suave, no un hueco. */
.pagina:not([data-dibujada]) {
    background-image: linear-gradient(100deg,
        var(--ui-bg-surface) 30%, var(--ui-bg-muted) 50%, var(--ui-bg-surface) 70%);
    background-size: 220% 100%;
    animation: barrido 1.4s ease-in-out infinite;
}

@keyframes barrido {
    from { background-position: 120% 0; }
    to { background-position: -60% 0; }
}

@media (prefers-reduced-motion: reduce) {
    .pagina:not([data-dibujada]) { animation: none; }
}

/* ── Capa de texto ──────────────────────────────────────────────────────────
   Va encima del dibujo, transparente. Existe para dos cosas: que se pueda
   seleccionar y copiar texto, y que la búsqueda tenga dónde pintar.

   El posicionamiento lo define vendor/pdfjs/capa-texto.css, que es copia
   textual de pdf.js y no se toca. Acá va sólo lo propio de esta aplicación. */
.textLayer { z-index: 2; }

.textLayer ::selection { background: var(--v-seleccion); }

/* El resaltado de la búsqueda. mix-blend-mode multiply deja ver el texto
   dibujado abajo en lugar de taparlo con un rectángulo opaco. */
.textLayer mark {
    background: var(--v-marca);
    color: transparent;
    border-radius: 2px;
    mix-blend-mode: multiply;
}

.textLayer mark[data-activa] {
    background: var(--v-marca-activa);
}

/* Sobre fondo oscuro, multiply ennegrece: screen es su equivalente. */
@media (prefers-color-scheme: dark) {
    :root[data-tema="auto"] .textLayer mark { mix-blend-mode: normal; }
}

/* ── Controles flotantes ────────────────────────────────────────────────── */
.controles {
    position: fixed;
    z-index: 20;
    left: 50%;
    bottom: calc(var(--v-espacio-md) + env(safe-area-inset-bottom));
    transform: translateX(-50%);

    display: flex;
    align-items: center;
    gap: var(--v-espacio-xs);
    padding: var(--v-espacio-xs);

    background: var(--ui-bg-surface);
    border: 1px solid var(--ui-border);
    border-radius: var(--ui-radius-pill);
    box-shadow: var(--ui-shadow-md);

    transition: opacity var(--ui-transition-normal), transform var(--ui-transition-normal);
}

/* Al desplazarse hacia abajo la barra se corre y deja leer; vuelve al parar o
   al subir. En una pantalla chica esos 3rem son media línea de texto. */
.controles[data-oculta] {
    opacity: 0;
    transform: translateX(-50%) translateY(150%);
    pointer-events: none;
}

.controles__grupo {
    display: flex;
    align-items: center;
    gap: var(--v-espacio-xs);
}

.controles__separador {
    width: 1px;
    height: 1.5rem;
    background: var(--ui-border);
}

/* En un teléfono la navegación por número de página no aparece: ahí el gesto
   natural es deslizar, que anda siempre y no depende de acertarle a un botón
   de la barra flotante. Los botones se quedan en escritorio, donde no hay
   gesto de deslizar cómodo y el documento se recorre con la rueda o el
   teclado.
   La consulta es por dispositivo señalador y no por ancho, igual que el botón
   de imprimir: una tablet con teclado se maneja como escritorio. Se oculta con
   CSS y no se saca del HTML porque app.js toma estos elementos por id al
   arrancar y falla ruidosamente si no están. */
.controles__grupo--escritorio,
.controles__separador { display: none; }

@media (pointer: fine) {
    .controles__grupo--escritorio { display: flex; }
    .controles__separador { display: block; }
}

.controles .boton-icono:hover:not(:disabled) { background: var(--ui-neutral-soft); }
.controles .boton-icono:active:not(:disabled) { background: var(--ui-accent-soft); }

.paginado {
    display: flex;
    align-items: center;
    gap: var(--v-espacio-xs);
    font-size: .8125rem;
    color: var(--ui-text-secondary);
    font-variant-numeric: tabular-nums;
}

/* "Página" es contexto para quien usa lector de pantalla; en la barra
   flotante de un teléfono no entra y el número solo se entiende igual. */
.paginado__etiqueta {
    position: absolute;
    width: 1px; height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
}

.paginado__entrada {
    width: 2.5rem;
    height: 2rem;
    padding: 0 var(--v-espacio-xs);
    text-align: center;
    border: 1px solid var(--ui-border-strong);
    border-radius: var(--ui-radius-xs);
    background: var(--ui-bg-muted);
    font-size: .875rem;
    font-variant-numeric: tabular-nums;
}

.paginado__entrada:focus {
    outline: none;
    border-color: var(--ui-focus-color);
    box-shadow: var(--ui-focus-ring);
}

.paginado__total { white-space: nowrap; }

.zoom-nivel {
    min-width: 3.25rem;
    height: 2rem;
    padding: 0 var(--v-espacio-sm);
    border: 0;
    border-radius: var(--ui-radius-xs);
    background: transparent;
    font-size: .8125rem;
    font-variant-numeric: tabular-nums;
    color: var(--ui-text-secondary);
    cursor: pointer;
}

.zoom-nivel:hover { background: var(--ui-neutral-soft); }

/* ── Estados ────────────────────────────────────────────────────────────── */
.estado {
    position: fixed;
    inset: 0;
    z-index: 30;
    display: grid;

    /* La columna se declara aunque haya una sola. Sin esto es `auto`, o sea
       max-content, y la caja de adentro llega hasta su max-width de 24rem: en un
       teléfono de 360 px eso son 64 px de desborde en cuanto el mensaje de error
       es largo. El `place-items: center` sigue centrando igual, pero ahora dentro
       de una pista que no puede pasarse de la pantalla. */
    grid-template-columns: minmax(0, 1fr);
    place-items: center;

    padding: var(--v-espacio-lg);
    background: var(--ui-bg-page);
}

.estado[hidden] { display: none; }

.estado__caja {
    max-width: 24rem;
    text-align: center;
}

/* ── Composición "hero" ─────────────────────────────────────────────────────
   No es una tarjeta chica flotando en el medio: ocupa la pantalla, con la marca
   arriba a la izquierda, el texto pegado al borde de lectura y una escena que
   explica la situación antes de que se lea una palabra.

   La usan los dos estados de pantalla completa —"no encontramos el prospecto" y
   "falta el código"— porque son estructuralmente lo mismo: marca, ilustración,
   título, mensaje y pie. Lo único que cambia entre ellos es qué escena se
   muestra y qué dice el texto, y eso lo decide app.js encendiendo y apagando
   piezas. Acá no hay una sola regla que sepa de cuál de los dos se trata.

   Los otros dos estados —la carga y el error genérico— siguen usando la caja
   centrada de arriba, sin enterarse de nada de esto.

   Por qué no hay `place-items: center` acá: centrando con grid, un contenido
   más alto que la ventana se desborda por arriba y esa parte queda inalcanzable
   aunque el contenedor tenga scroll. En lugar de eso el contenedor sólo
   desplaza, y de repartir el alto se encarga la caja con `min-height: 100%` más
   un `align-content` distinto según el tamaño: `space-between` en el teléfono
   —donde el contenido casi llena y conviene esparcir lo poco que sobra— y
   `center` en pantalla grande, donde sobra mucho y hay que dejarlo afuera del
   bloque. Con las dos, un contenido más alto que la ventana simplemente crece
   hacia abajo y se desplaza: nunca se recorta.                                */
.estado--hero {
    display: block;
    overflow-y: auto;

    /* Explícito y no heredado. Con overflow-y en auto y overflow-x sin declarar,
       el `visible` de x se convierte solo en `auto` y la pantalla queda
       desplazable de costado: cualquier cosa que se pase un píxel se lleva
       puesta la composición entera en un teléfono. Acá nada tiene que salirse
       hacia los lados, así que se recorta y listo. */
    overflow-x: hidden;

    overscroll-behavior: contain;
    padding: 0;

    /* Los degradados usan --ui-accent-soft y NO --v-ilus-halo. Con el mismo color
       en los dos lados, el círculo de la ilustración quedaba encima de una zona
       de su mismo tono y directamente no se veía: el halo tiene que ser más
       saturado que cualquier cosa que haya detrás. */
    background-color: var(--ui-bg-page);
    background-image:
        radial-gradient(70rem 46rem at 82% 22%, var(--ui-accent-soft), transparent 62%),
        radial-gradient(52rem 36rem at 4% 98%, var(--ui-accent-soft), transparent 64%);
    background-repeat: no-repeat;
}

/* Los dos parches de puntitos de las esquinas. Van en pseudoelementos y no en
   una capa más del background porque un patrón que se repite no se puede
   recortar a una zona con background-repeat solo.

   Sólo en pantalla grande: en un teléfono no hay esquinas vacías que llenar y
   los puntos terminan compitiendo con el contenido en vez de acompañarlo. */
.estado--hero::before,
.estado--hero::after {
    content: "";
    display: none;
    position: absolute;
    z-index: 0;
    width: clamp(5rem, 12vw, 8.5rem);
    height: clamp(3.5rem, 8vw, 5.5rem);
    background-image: radial-gradient(var(--v-punteado) 1.5px, transparent 1.6px);
    background-size: 1.3rem 1.3rem;
    pointer-events: none;
}

.estado--hero::before { top: 7%; right: 5%; }
.estado--hero::after { bottom: 9%; left: 4%; }

.estado--hero .estado__caja {
    position: relative;
    z-index: 1;

    display: grid;

    /* svh y no 100%: el contenedor de arriba es `fixed` y por lo tanto mide el
       viewport GRANDE, el de cuando la barra de direcciones está escondida. Con
       la barra a la vista —o sea al entrar— la franja de abajo cae fuera de lo
       visible y el pie de contacto queda debajo del pliegue sin que nada lo
       delate. `svh` es el viewport chico, el que siempre se ve. El 100% queda
       de reserva para navegadores que no conozcan la unidad. */
    min-height: 100%;
    min-height: 100svh;

    justify-items: start;

    width: 100%;
    max-width: 75rem;
    margin-inline: auto;

    /* El margen lateral, en una variable porque la ilustración lo necesita para
       poder salirse de él. ← subilo o bajalo acá y se mueven las dos cosas.

       El error original era usar 5vw: en cualquier pantalla de teléfono ese
       término queda por debajo del mínimo, así que el clamp se resolvía siempre
       al piso y todos los celulares —de 320 a 430— terminaban con el mismo
       margen sin importar el ancho. Con 8vw acompaña de verdad: 26 px en un 320,
       31 en un 390, 34 en un 430. El techo de 3.5rem lo corta a partir de 700,
       donde el que manda pasa a ser el max-width de la caja. */
    --v-borde: clamp(1.5rem, 8vw, 3.5rem);

    /* max() contra las safe areas: en un iPhone apaisado el notch se come el
       costado y el título arrancaría debajo de la muesca. env() vale 0 donde no
       existe, así que el max() se resuelve al clamp de siempre. */
    padding:
        max(clamp(1.25rem, 4vh, 3rem), env(safe-area-inset-top))
        max(var(--v-borde), env(safe-area-inset-right))
        max(clamp(1.25rem, 4vh, 3rem), env(safe-area-inset-bottom))
        max(var(--v-borde), env(safe-area-inset-left));

    text-align: start;

    grid-template-columns: minmax(0, 1fr);
    grid-template-areas:
        "marca"
        "ilustracion"
        "contenido"
        "contacto";

    /* Filas al natural y el sobrante repartido entre los huecos.

       Acá hubo una fila elástica para la ilustración, con la idea de que
       absorbiera lo que faltara. No funciona: un `1fr` reparte el espacio que
       SOBRA, no obliga a un elemento a encogerse. Con poco alto el `1fr` se
       quedaba en su mínimo, la escena conservaba su tamaño natural y el pie se
       iba abajo del pliegue igual. Quien tiene que ceder es la ilustración, y
       eso se resuelve en su propio tamaño (ver más abajo), no en la grilla.

       `space-between` deja la marca pegada arriba, el contacto pegado abajo y
       divide lo que sobra entre los huecos del medio. Si no sobra nada, se
       comporta como `start` y nada se recorta. */
    align-content: space-between;
}

/* En este estado no van, y decirlo explícitamente evita que si alguna vez se
   encienden por error aparezcan como filas fantasma de la grilla. */
.estado--hero .estado__animacion,
.estado--hero .estado__icono { display: none; }

.estado--hero .estado__marca { grid-area: marca; }
.estado--hero .estado__ilustracion { grid-area: ilustracion; }
.estado--hero .estado__contenido { grid-area: contenido; }
.estado--hero .estado__contacto { grid-area: contacto; }

/* ── Marca ──────────────────────────────────────────────────────────────── */
.estado__marca { display: none; }

.estado--hero .estado__marca {
    display: block;
    margin: 0 0 clamp(1.25rem, 4vh, 2.5rem);
    animation: estado-entra .42s cubic-bezier(.2, .8, .2, 1) both;
}

/* Sólo el ancho: el alto queda en auto y el archivo trae su proporción en los
   atributos width/height, así que el logo no se deforma nunca. */
.estado__marca img {
    display: block;
    width: clamp(10.5rem, 3rem + 15vw, 14rem);
    height: auto;
    filter: var(--v-marca-logo-filtro);
}

/* ── Ilustración ────────────────────────────────────────────────────────────
   Es lo único que puede ceder alto sin romperse, así que es lo que cede.     */
.estado--hero .estado__ilustracion {
    /* Tres topes, y el tercero es el que hace que todo entre sin desplazar.

       El SVG no tiene alto propio: su alto sale del ancho por la proporción del
       viewBox (292 × 230). O sea que para limitar cuánto alto se come hay que
       limitarle el ANCHO, y por eso el tope va acá y no en un max-height.

       38svh de ancho son unos 30svh de alto —menos de un tercio de la pantalla—
       y con eso el resto del bloque entra completo hasta en un teléfono de 640
       px de alto útil. Es un número para tocar si querés la escena más grande o
       más chica: cada punto son ~2,5 px de ancho en un celular típico.

       svh y no vh: vh mide el viewport GRANDE, el de cuando la barra de
       direcciones está escondida. Dimensionar con eso es lo que dejaba el pie
       justo abajo del pliegue al entrar, que es cuando la barra está a la vista.

       Los otros dos topes siguen mandando donde corresponde: el ancho en un
       teléfono angosto y alto, y las 27rem en tablet.

       Sobre el primero: la escena se sale del margen a propósito. El texto
       necesita el gutter para no pegarse al borde; una ilustración no se lee, y
       encerrarla en la misma columna la dejaba chica y con aire de sobra a los
       costados. `100% + el margen` la hace un margen más ancha que su celda y,
       centrada, ese excedente se reparte en dos: se come la mitad del gutter de
       cada lado y llega a unos 15 px del borde. */
    width: min(calc(100% + var(--v-borde)), 27rem, 38svh);
    justify-self: center;

    /* Separación vertical, así que en vh y no en vw: tiene que responder al alto
       disponible. Estaban todas en vw y por eso no se ajustaban nunca. */
    margin: 0 0 clamp(1rem, 2.5vh, 2.25rem);
    color: var(--ui-accent);
}

.estado__ilustracion[hidden] { display: none; }

/* El alto sale del ancho por la proporción del viewBox: limitando el ancho del
   contenedor queda limitado el alto de la escena, que es de lo que depende que
   el resto del bloque entre en pantalla. */
.estado__ilustracion svg {
    display: block;
    width: 100%;
    height: auto;
    overflow: visible;
    fill: none;
    stroke: currentColor;
    stroke-width: 3;
    stroke-linecap: round;
    stroke-linejoin: round;
}

/* La escena es de formas rellenas, no de contornos: el papel es una superficie
   blanca con sombra, las hojas y el QR son manchas de color, y sólo la lupa y
   los renglones se dibujan con trazo, porque ahí el trazo ES la forma. El
   `fill: none / stroke: currentColor` de la regla de arriba es el valor por
   omisión y cada parte se lo pisa según lo que sea.                           */
.estado__halo {
    fill: var(--v-ilus-halo);
    stroke: none;
    animation: estado-aparece-suave .5s ease-out both;
}

.estado__hojas {
    fill: var(--v-ilus-hoja);
    stroke: none;
    animation: estado-aparece-suave .5s ease-out .08s both;
}

.estado__hoja--suave { fill: var(--v-ilus-suave); }

.estado__papel {
    transform-box: fill-box;
    transform-origin: center;
    animation: estado-papel .52s cubic-bezier(.2, .8, .2, 1) .06s both;
}

/* Los tres vaivenes tienen períodos primos entre sí (6.5 / 5 / 3.6 s) para que
   no se sincronicen: si coincidieran, la escena entera latiría junta y eso se
   lee como una alarma en vez de como algo vivo. */
.estado__papel-vaiven {
    transform-box: fill-box;
    transform-origin: center;
    animation: estado-flota 6.5s ease-in-out .9s infinite;
}

/* Una copia corrida del papel hace de sombra. No es un filtro de desenfoque a
   propósito: un feGaussianBlur en un SVG que se escala a media pantalla obliga
   al navegador a rasterizar la capa, y en un teléfono de gama baja eso se nota
   más que la diferencia visual entre una sombra suave y una plana. */
.estado__papel-sombra {
    fill: var(--v-ilus-sombra);
    stroke: none;
}

.estado__papel-hoja {
    fill: var(--ui-bg-surface);
    stroke: var(--v-ilus-borde);
    stroke-width: 2;
}

.estado__papel-pliegue {
    fill: var(--v-ilus-suave);
    stroke: none;
}

/* Renglones como barras redondeadas y no como líneas finas: de lejos tienen que
   leerse como bloques de texto. Más claros que las hojas a propósito: son lo
   que está DENTRO del papel y no pueden pesar más que el papel mismo. */
.estado__papel-linea {
    stroke: var(--v-ilus-renglon);
    stroke-width: 5.5;
}

.estado__lupa {
    transform-box: fill-box;
    transform-origin: center;
    animation: estado-lupa .46s cubic-bezier(.2, .8, .2, 1) .26s both;
}

.estado__lupa-vaiven {
    transform-box: fill-box;
    transform-origin: center;
    animation: estado-vaiven 5s ease-in-out .8s infinite;
}

/* El acento y no --ui-brand-dark. El azul marino es tan oscuro que la lupa se
   leía casi negra y rompía la familia de azules del resto de la escena; el
   contraste con la X lo da el color, no la oscuridad. El marino queda para el
   QR, que es lo único que tiene que verse impreso.

   Los grosores acompañan al radio: si no, una lupa chica se ve dibujada con
   marcador grueso. */
.estado__lupa-cristal {
    fill: var(--ui-bg-surface);
    fill-opacity: .96;
    stroke: var(--ui-accent);
    stroke-width: 8.5;
}

.estado__lupa-mango {
    stroke: var(--ui-accent);
    stroke-width: 13;
}

.estado__lupa-reflejo {
    stroke: var(--v-ilus-suave);
    stroke-width: 4;
}

/* Lo único rojo de toda la escena y el punto de mayor contraste: es lo que
   tiene que mirarse primero. */
.estado__lupa-cruz {
    stroke: var(--v-ilus-x);
    stroke-width: 9;
    transform-box: fill-box;
    transform-origin: center;
    animation: estado-cruz .38s cubic-bezier(.34, 1.4, .64, 1) .54s both;
}

/* Más claros y más finos que las hojas: son el último plano de la escena y si
   pesan lo mismo que algo con forma, el ojo los cuenta como contenido. */
.estado__destellos {
    stroke: var(--v-ilus-suave);
    stroke-width: 4.5;
    animation: estado-aparece-suave .34s ease-out .64s both;
}

.estado__destellos circle { fill: var(--v-ilus-suave); stroke: none; }

/* ── Código QR ──────────────────────────────────────────────────────────────
   Lo usan las dos escenas: chico dentro del prospecto en el 404, grande sobre
   el estuche en la pantalla de inicio. Mismas clases, distinta geometría.

   La opacidad baja el marino un escalón. Sin eso el QR es el único negro de una
   composición de azules claros y se lleva la mirada entera: el punto más oscuro
   tiene que empatar con el aro de la lupa, no ganarle, y el acento fuerte de la
   escena ser la X.                                                            */
.estado__qr {
    fill: var(--ui-brand-dark);
    stroke: none;
    opacity: .88;
}

/* El hueco blanco de los patrones de posición. No es un agujero de verdad
   —sería fill-rule y dos subtrazados— sino un cuadrado del color del papel
   encima: más simple de leer y de tocar. */
.estado__qr-hueco { fill: var(--ui-bg-surface); }

.estado__mira {
    stroke: var(--ui-accent);
    stroke-width: 6;
    fill: none;
}

/* Lo único que se mueve de esta escena, y no es adorno: una línea que barre un
   código dice "escaneá" sin escribirlo, que es exactamente lo que hay que
   pedirle a la persona acá.

   transform-box queda en view-box (el valor inicial) y no en fill-box: la caja
   de una línea horizontal tiene alto cero y el desplazamiento sobre ella no
   está definido de la misma forma en todos los navegadores. */
.estado__mira-linea {
    stroke: var(--ui-accent);
    stroke-width: 4;
    opacity: .85;
    transform-box: view-box;
    animation: estado-barre 2.6s ease-in-out 1s infinite;
}

/* El titileo arranca a partir del segundo, cuando la entrada ya terminó, y no
   para más: es lo único que se mueve solo en toda la pantalla y lo que evita
   que la escena se lea como una captura pegada. */
.estado__destellos > * {
    transform-box: fill-box;
    transform-origin: center;
    animation: estado-titila 3.6s ease-in-out 1s infinite;
}

.estado__destellos > :nth-child(2) { animation-delay: 1.5s; }
.estado__destellos > :nth-child(3) { animation-delay: 2.1s; }
.estado__destellos > :nth-child(4) { animation-delay: 2.7s; }
.estado__destellos > :nth-child(5) { animation-delay: 3.3s; }
.estado__destellos > :nth-child(6) { animation-delay: 1.8s; }
.estado__destellos > :nth-child(7) { animation-delay: 2.4s; }

/* ── Columna de texto ───────────────────────────────────────────────────── */
/* El `minmax(0, 1fr)` no es decorativo, arregla un desborde real.

   Sin declarar columnas, la columna implícita de una grilla es `auto`, y `auto`
   se dimensiona por el max-content de lo que tenga adentro. El max-content de un
   título es su ancho SIN CORTAR: la pista crecía hasta ese ancho, se pasaba de
   la pantalla, el título salía en un renglón y el formulario —que hereda la
   misma pista— empujaba el botón "Ver" afuera del borde.

   El `0` de mínimo es la otra mitad: sin él, la pista tampoco puede achicarse
   por debajo del min-content de sus hijos, que es el clásico desborde de un
   input adentro de una grilla. */
.estado--hero .estado__contenido {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    justify-items: start;
    width: 100%;
}

/* Red de seguridad: ningún hijo del bloque de texto puede pasarse de su
   columna, venga de donde venga el ancho. */
.estado--hero .estado__contenido > * { max-width: 100%; }

/* El código consultado, en rojo y con el ⚠ al lado. El ícono va junto al texto
   y no suelto: solo, un ícono rojo no dice nada. */
.estado--hero .estado__etiqueta {
    display: inline-flex;
    align-items: center;
    gap: .5rem;
    max-width: 100%;
    margin: 0 0 clamp(.375rem, 1vh, .75rem);
    font-size: clamp(.875rem, .8rem + .25vw, 1rem);
    font-weight: 700;
    color: var(--ui-danger);
    animation: estado-entra .42s cubic-bezier(.2, .8, .2, 1) .08s both;
}

.estado__etiqueta[hidden] { display: none; }

.estado__etiqueta svg {
    width: 1.125rem;
    height: 1.125rem;
    flex: none;
}

/* Monoespaciado y con las cifras de ancho fijo: se va a comparar dígito por
   dígito contra lo impreso en la caja, y en una tipografía proporcional el 1
   ocupa la mitad que el 8 y la vista se pierde. */
.estado__codigo {
    font-family: var(--ui-font-family-mono);
    font-variant-numeric: tabular-nums;
    letter-spacing: .04em;
    word-break: break-all;
}

.estado--hero .estado__titulo {
    margin: 0;

    /* Del piso de 28 px en una pantalla de 320 al techo de 56 px, que es donde
       "No encontramos el prospecto" deja de ganar presencia y empieza a comerse
       la ilustración. */
    font-size: clamp(1.75rem, 1.1rem + 3vw, 3.5rem);
    line-height: 1.08;
    letter-spacing: -.02em;
    font-weight: 700;
    color: var(--ui-brand-dark);
    text-wrap: balance;
    animation: estado-entra .42s cubic-bezier(.2, .8, .2, 1) .14s both;
}

/* El acento azul del mockup. Va como pseudoelemento del título y no como un
   <hr> para que no aparezca en los otros dos estados ni lo lea nadie. */
.estado--hero .estado__titulo::after {
    content: "";
    display: block;
    width: 3.5rem;
    height: .1875rem;
    margin: clamp(.75rem, 1.8vh, 1.375rem) 0;
    border-radius: var(--ui-radius-pill);
    background: var(--ui-accent);
}

.estado--hero .estado__mensaje {
    /* Medida de lectura, no ancho de columna: pasadas las ~40 palabras por
       renglón el ojo pierde el salto de línea. `balance` y no `pretty` porque
       son dos o tres renglones y lo que importa es que queden parejos, no que
       no sobre una palabra sola al final. */
    max-width: 30ch;
    margin: 0;
    font-size: clamp(1rem, .96rem + .2vw, 1.125rem);
    color: var(--ui-text-secondary);
    text-wrap: balance;
    animation: estado-entra .42s cubic-bezier(.2, .8, .2, 1) .2s both;
}

/* ── Bloque de ayuda ────────────────────────────────────────────────────── */
.estado__ayuda {
    display: flex;
    align-items: flex-start;
    gap: .75rem;
    width: 100%;
    max-width: 30rem;
    margin-top: clamp(.875rem, 2.2vh, 1.625rem);

    /* Recuperado después de sacarle el título: con el padding ajustado, una caja
       de un solo renglón se leía como un pie de página en vez de como la
       instrucción de qué hacer, que es lo único accionable del bloque. */
    padding: clamp(.8125rem, 1.8vh, 1.125rem) clamp(1rem, 2.5vw, 1.25rem);

    /* Borde en el azul exacto del halo y relleno un tono más claro: la caja
       queda en la misma familia que la ilustración de arriba. Un gris neutro
       la desconectaba del resto y --ui-accent-soft pesaba demasiado. */
    border: 1px solid var(--v-ilus-halo);
    border-radius: var(--ui-radius-md);
    background: var(--v-ayuda-fondo);
    text-align: start;
    animation: estado-entra .42s cubic-bezier(.2, .8, .2, 1) .26s both;
}

.estado__ayuda[hidden] { display: none; }

.estado__ayuda-icono {
    width: 1.375rem;
    height: 1.375rem;
    flex: none;
    margin-top: .0625rem;
}

.estado__ayuda-disco {
    fill: var(--ui-accent);
    stroke: none;
}

.estado__ayuda-i {
    stroke: var(--ui-text-on-accent);
    stroke-width: 2.4;
}

.estado__ayuda-texto {
    margin: 0;
    font-size: .9375rem;
    line-height: 1.5;
    color: var(--ui-text-secondary);
}

/* ── Acceso sin código ──────────────────────────────────────────────────────
   Las dos formas de entrar cuando no vino ningún código en la URL.           */
/* El minmax(0, 1fr), otra vez y por lo mismo. Una grilla sin columnas declaradas
   dimensiona su columna implícita por max-content, y acá el max-content es el
   ancho natural de un <input> —unos 20 caracteres— más el botón: bastante más
   que una pantalla de teléfono. El max-width de abajo recorta la caja pero no lo
   que se derrama adentro, así que sin esto el botón se salía igual. */
.estado__acceso {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: .75rem;
    width: 100%;
    max-width: 26rem;
    margin-top: clamp(.875rem, 2.2vh, 1.625rem);
    animation: estado-entra .42s cubic-bezier(.2, .8, .2, 1) .26s both;
}

.estado__acceso[hidden] { display: none; }

.estado__acceso-camara {
    width: 100%;
    gap: .5rem;
}

.estado__acceso-camara[hidden] { display: none; }

/* min-width: 0 porque un contenedor flex, por omisión, no se deja achicar por
   debajo del contenido de sus hijos. Es la otra cara del mismo problema: el
   input trae un ancho natural y sin esto lo impone hacia arriba. */
.estado__manual {
    display: flex;
    gap: .5rem;
    width: 100%;
    min-width: 0;
}

/* El botón no se achica: es el que tiene el largo fijo y corto. Sin esto flex
   reparte el faltante entre los dos y "Ver" termina cortado antes que el campo,
   que es justo al revés de lo que conviene. */
.estado__manual .boton {
    flex: none;
    padding-inline: var(--v-espacio-md);
}

.estado__manual-entrada {
    flex: 1;
    min-width: 0;
    height: 2.75rem;
    padding: 0 var(--v-espacio-md);
    border: 1px solid var(--ui-border-strong);
    border-radius: var(--ui-radius-sm);
    background: var(--ui-bg-surface);

    /* 16 px clavados: con menos, iOS hace zoom al enfocar y descoloca la
       pantalla entera. Mismo motivo que en el campo del buscador. */
    font-size: 1rem;
    font-variant-numeric: tabular-nums;
}

.estado__manual-entrada:focus {
    outline: none;
    border-color: var(--ui-focus-color);
    box-shadow: var(--ui-focus-ring);
}

.estado__manual-entrada[aria-invalid="true"] { border-color: var(--ui-danger); }

.estado__manual-error {
    margin: 0;
    font-size: .875rem;
    line-height: 1.45;
    color: var(--ui-danger);
}

.estado__manual-error[hidden] { display: none; }

/* ── Contacto ───────────────────────────────────────────────────────────────
   Cierra la composición, separado del resto por una línea. Es la única acción
   real de toda la pantalla, y existe únicamente si hay una URL configurada. */
.estado--hero .estado__contacto {
    display: flex;
    align-items: center;
    gap: .5rem;
    justify-self: stretch;
    margin: clamp(1rem, 2.6vh, 2.25rem) 0 0;
    padding-top: clamp(.75rem, 1.8vh, 1.25rem);
    border-top: 1px solid var(--ui-border);
    font-size: .9375rem;
    color: var(--ui-text-secondary);
    animation: estado-entra .42s cubic-bezier(.2, .8, .2, 1) .32s both;
}

.estado__contacto[hidden] { display: none; }

.estado__contacto-icono {
    width: 1.25rem;
    height: 1.25rem;
    flex: none;
    color: var(--ui-text-muted);
}

/* Sin negrita: el color y el subrayado ya dicen que es un enlace, y el peso
   extra lo convertía en lo más fuerte del pie, por encima del mensaje. */
.estado__contacto a {
    color: var(--ui-accent);
    text-underline-offset: .2em;
}

.estado__contacto a:hover { color: var(--ui-accent-hover); }

/* ── Composición de escritorio ──────────────────────────────────────────────
   Un solo punto de quiebre en todo el estado. Debajo de 1024 la escena y el
   texto uno abajo del otro entran bien —incluso en una tablet vertical— y
   partir en dos columnas antes deja los dos lados demasiado angostos.

   La marca cruza las dos columnas y se queda arriba de todo; el resto se
   reparte y la fila `1fr` es la que centra el bloque verticalmente.          */
@media (min-width: 64rem) {
    /* Al revés que en el teléfono: acá el sobrante NO se reparte, se deja
       entero arriba y abajo.

       En una pantalla ancha hay mucho alto libre y poco contenido. Con la marca
       anclada al borde y el resto centrado quedaban 200 px de nada entre las dos
       cosas y el logo se leía abandonado. Centrando el conjunto —marca, héroe y
       pie— las tres partes viajan juntas y el vacío queda afuera, que es donde
       no molesta. */
    .estado--hero .estado__caja {
        grid-template-columns: minmax(0, 1fr) minmax(0, 1.05fr);
        grid-template-rows: auto auto auto;
        align-content: center;
        column-gap: clamp(2rem, 5vw, 5rem);
        grid-template-areas:
            "marca      marca"
            "contenido  ilustracion"
            "contacto   contacto";
    }

    .estado--hero::before,
    .estado--hero::after { display: block; }

    /* Centrado al pie, cruzando las dos columnas. */
    .estado--hero .estado__contacto { justify-content: center; }

    .estado--hero .estado__contenido {
        align-content: center;
        padding-bottom: clamp(1.5rem, 4vh, 3rem);
    }

    /* El tope en vh es para las notebooks de poca altura: en 1366×768 la escena
       se queda en unos 540 px de ancho y no empuja nada fuera de la pantalla. */
    /* En escritorio la escena comparte renglón con el texto en vez de apilarse
       encima, así que puede ser mucho más grande: el tope en svh acá cuida las
       notebooks de poca altura, no los teléfonos. */
    .estado--hero .estado__ilustracion {
        width: 100%;
        max-width: min(39rem, 76svh);
        margin: 0;
        align-self: center;
        justify-self: end;
    }
}

.estado__icono {
    width: 3rem;
    height: 3rem;
    color: var(--ui-text-muted);
    margin-bottom: var(--v-espacio-md);
}

.estado--error .estado__icono { color: var(--ui-danger); }

.estado__titulo {
    margin: 0 0 var(--v-espacio-sm);
    font-size: 1.25rem;
    font-weight: 600;
    color: var(--ui-brand-dark);
}

.estado__mensaje {
    margin: 0;
    color: var(--ui-text-secondary);
}

.estado__acciones {
    margin-top: var(--v-espacio-lg);
}

.estado__acciones[hidden] { display: none; }

/* Tres puntos que laten: pesa menos que un spinner y no gira, que en una
   espera corta se lee más tranquilo. */
.estado__animacion {
    display: flex;
    justify-content: center;
    gap: var(--v-espacio-sm);
    margin-bottom: var(--v-espacio-lg);
}

.estado__animacion[hidden] { display: none; }

.estado__animacion span {
    width: .625rem;
    height: .625rem;
    border-radius: 50%;
    background: var(--ui-accent);
    animation: latido 1.2s ease-in-out infinite;
}

.estado__animacion span:nth-child(2) { animation-delay: .15s; }
.estado__animacion span:nth-child(3) { animation-delay: .3s; }

@keyframes latido {
    0%, 80%, 100% { opacity: .25; transform: scale(.8); }
    40% { opacity: 1; transform: scale(1); }
}

/* ── Entrada del 404 ────────────────────────────────────────────────────────
   Todo es de entrada y nada queda en bucle. Una escena que se mueve para
   siempre en la pantalla de un error termina siendo el motivo por el que
   alguien cierra la pestaña, y en un teléfono además consume batería mientras
   la persona lee.

   El último fotograma cae en 980 ms —los destellos, que arrancan a los 640— y a
   partir de ahí la escena queda quieta. Todos usan `both`, así que el elemento
   está invisible durante su retardo y no hay parpadeo antes de empezar.

   Ninguna anima ancho, alto ni posición: sólo opacidad y transform, que no
   provocan reflow y por lo tanto no mueven el texto de abajo.                */
@keyframes estado-aparece-suave {
    from { opacity: 0; }
}

@keyframes estado-entra {
    from { opacity: 0; transform: translateY(.625rem); }
}

@keyframes estado-papel {
    from { opacity: 0; transform: translateY(14px); }
}

@keyframes estado-lupa {
    0% { opacity: 0; transform: translate(-10px, 8px) scale(.94); }
    55% { opacity: 1; transform: translate(3px, -3px) scale(1.02); }
    100% { opacity: 1; transform: translate(0, 0) scale(1); }
}

/* El rebote lo hace la curva (.34, 1.4, .64, 1), que se pasa un poco de 1 y
   vuelve. Un solo sobrepaso y chico: la X marca un error, no lo festeja. */
@keyframes estado-cruz {
    from { opacity: 0; transform: scale(.8); }
}

/* ── Vida propia ────────────────────────────────────────────────────────────
   Los tres bucles que quedan después de la entrada. Las distancias están en
   unidades del viewBox, no en píxeles de pantalla: 4 sobre 340 es medio punto
   porcentual, se percibe pero no se mide.                                    */
@keyframes estado-flota {
    0%, 100% { transform: translateY(0) rotate(0deg); }
    50% { transform: translateY(-4px) rotate(.8deg); }
}

@keyframes estado-vaiven {
    0%, 100% { transform: translate(0, 0) rotate(0deg); }
    50% { transform: translate(3px, -5px) rotate(-2deg); }
}

@keyframes estado-titila {
    0%, 100% { opacity: .4; transform: scale(.85); }
    50% { opacity: 1; transform: scale(1.15); }
}

/* Los 92 son el alto útil de la mira en unidades del viewBox. Se desvanece en
   los extremos para que el rebote no se vea como un golpe contra el marco. */
@keyframes estado-barre {
    0%, 100% { transform: translateY(0); opacity: .25; }
    12%, 88% { opacity: .9; }
    50% { transform: translateY(92px); opacity: .9; }
}

/* Con la preferencia de movimiento reducido no se apaga todo: se cambia sólo el
   juego de fotogramas por el fundido puro, conservando duración y retardo. La
   preferencia pide MENOS movimiento, no ausencia de transición, y un cambio de
   opacidad no desplaza nada en pantalla. Así la pantalla sigue apareciendo de
   forma ordenada en vez de aparecer de golpe y entera.

   (Las tres partes que ya usan `estado-aparece-suave` no hace falta listarlas.)

   Los selectores repiten la especificidad de las reglas de arriba y este bloque
   está más abajo en el archivo, así que gana por orden. Sin eso, pisar una
   animación declarada con dos clases desde un selector de una no funcionaría. */
@media (prefers-reduced-motion: reduce) {
    .estado__animacion span { animation: none; opacity: .6; }

    .estado--hero .estado__marca,
    .estado--hero .estado__etiqueta,
    .estado--hero .estado__titulo,
    .estado--hero .estado__mensaje,
    .estado--hero .estado__contacto,
    .estado__ayuda,
    .estado__acceso,
    .estado__papel,
    .estado__lupa,
    .estado__lupa-cruz { animation-name: estado-aparece-suave; }

    /* Los bucles sí se apagan del todo. Un fundido de entrada que dura menos de
       un segundo es una cosa; algo que se mueve para siempre en la pantalla es
       exactamente lo que la preferencia pide que no pase.

       La línea que barre el QR entra acá aunque signifique algo: con la
       preferencia puesta, el marco de la mira y el texto alcanzan para entender
       que hay que escanear. */
    .estado__papel-vaiven,
    .estado__lupa-vaiven,
    .estado__mira-linea,
    .estado__destellos > * { animation: none; }
}

/* ── Escáner ────────────────────────────────────────────────────────────────
   Capa a pantalla completa por encima de todo. Fondo casi negro y no la
   superficie clara de siempre: lo único que importa acá es ver bien lo que capta
   la cámara, y cualquier claro alrededor le baja el contraste a la imagen.    */
.escaner {
    position: fixed;
    inset: 0;
    z-index: 50;
    display: grid;
    place-items: center;
    background: #06121d;
}

.escaner[hidden] { display: none; }

/* object-fit: cover llena la pantalla sin deformar. El recorte no molesta:
   apuntar es un gesto continuo y lo que queda afuera del borde vuelve a entrar
   con mover un poco la mano. */
.escaner__video {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

/* Las cuatro esquinas de la mira. Encuadran sin tapar: un marco cerrado
   escondería justo el código que hay que leer. */
.escaner__marco {
    position: relative;
    width: min(72vw, 72vh, 20rem);
    aspect-ratio: 1;
}

.escaner__marco span {
    position: absolute;
    width: 2rem;
    height: 2rem;
    border: 3px solid var(--ui-text-on-fill);
    border-radius: 3px;
}

.escaner__marco span:nth-child(1) { top: 0; left: 0; border-right: 0; border-bottom: 0; }
.escaner__marco span:nth-child(2) { top: 0; right: 0; border-left: 0; border-bottom: 0; }
.escaner__marco span:nth-child(3) { bottom: 0; left: 0; border-right: 0; border-top: 0; }
.escaner__marco span:nth-child(4) { bottom: 0; right: 0; border-left: 0; border-top: 0; }

.escaner__ayuda {
    position: absolute;
    left: 50%;
    bottom: calc(6.5rem + env(safe-area-inset-bottom));
    transform: translateX(-50%);

    max-width: min(90%, 24rem);
    margin: 0;
    padding: .625rem 1rem;
    border-radius: var(--ui-radius-pill);

    /* Pastilla oscura detrás del texto: sobre el video en vivo, cualquier color
       plano puede quedar arriba de un fondo claro y volverse ilegible. */
    background: rgb(6 18 29 / .72);
    color: var(--ui-text-on-fill);
    font-size: .9375rem;
    text-align: center;
}

.escaner__cerrar {
    position: absolute;
    left: 50%;
    bottom: calc(1.5rem + env(safe-area-inset-bottom));
    transform: translateX(-50%);

    min-width: 9rem;
    border-color: rgb(255 255 255 / .35);
    background: rgb(255 255 255 / .12);
    color: var(--ui-text-on-fill);
}

.escaner__cerrar:hover { background: rgb(255 255 255 / .2); }

/* ── Pantalla grande ────────────────────────────────────────────────────── */
@media (min-width: 30rem) {
    .marca__cola { display: inline; }
}

@media (min-width: 48rem) {
    .documento {
        padding-left: var(--v-espacio-xl);
        padding-right: var(--v-espacio-xl);
    }

    .barra {
        padding: 0 var(--v-espacio-lg);
        gap: var(--v-espacio-md);
    }

    .marca { gap: 1rem; --v-logo-alto: 2rem; }
    .marca__seccion { font-size: 1.0625rem; }
}

/* En un teléfono no hay impresora: el botón se muestra sólo donde puede
   funcionar de verdad. La consulta es por dispositivo señalador fino, no por
   ancho, porque una tablet con teclado sí imprime. */
.boton-icono--escritorio { display: none; }

@media (pointer: fine) {
    .boton-icono--escritorio { display: inline-flex; }
}

/* ── Impresión ──────────────────────────────────────────────────────────────
   La impresión real se hace con un iframe aparte que contiene el PDF original
   (ver acciones.js). Esta hoja sólo se asegura de que, si alguien igual manda
   a imprimir la página, no salga la interfaz. */
@media print {
    .barra, .controles, .estado { display: none !important; }
    .documento { height: auto; overflow: visible; padding: 0; }
    .pagina { box-shadow: none; border-radius: 0; break-inside: avoid; }
}
