/* ==========================================================================
   RUMBOH · Portal — COMPONENTES                                 (HU-PC-101)
   ==========================================================================
   Las piezas que se repiten en todo el Portal: encabezado, pie, tarjetas,
   botones, campos y avisos. Todo color sale de tokens.css.

   🔴 Se escriben a mano y no con una biblioteca de componentes. Radzen esta
   disponible y se usara donde de verdad gane —la rejilla de rutas, el visor de
   camaras, los dialogos—, pero las pantallas de entrada del Portal (acceso,
   registro, recuperacion) son formularios de seis campos que ademas tienen que
   funcionar en RENDER ESTATICO. Envolverlos en componentes interactivos
   agregaria peso y marcado sobre el que no se controla el detalle de ARIA, en
   las cuatro paginas que ve TODO ciudadano y donde la conformidad AA se revisa
   primero.
   ========================================================================== */

/* ==========================================================================
   ENCABEZADO
   ========================================================================== */
.encabezado {
    position: sticky;
    top: 0;
    z-index: 50;
    background: var(--superficie);
    border-bottom: 1px solid var(--borde-sutil);
}
.encabezado__barra {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--e-4);
    min-height: var(--alto-encabezado);
}

/* La marca es un enlace a Inicio: es lo que espera cualquiera que haya usado
   un sitio web, y ahorra un elemento de navegacion en el telefono. */
.marca {
    display: inline-flex;
    align-items: center;
    gap: var(--e-3);
    text-decoration: none;
    color: var(--texto);
    font-weight: 700;
    letter-spacing: -.01em;
}
.marca__logo { width: 2.25rem; height: 2.25rem; border-radius: var(--r-sm); }
.marca__nombre { font-size: var(--txt-lg); }

/* ⚠️ El descriptor se oculta en telefono por espacio, NO se elimina: sigue en
   el marcado para la ayuda tecnica y para el buscador. Ocultar con display
   none lo quitaria tambien del lector de pantalla. */
.marca__descriptor {
    display: none;
    font-size: var(--txt-xs);
    font-weight: 400;
    color: var(--texto-secundario);
}
@media (min-width: 48rem) {
    .marca__descriptor { display: block; }
    .marca { flex-direction: row; align-items: baseline; }
}

.encabezado__acciones { display: flex; align-items: center; gap: var(--e-2); }

/* ── 🔴 SC 1.4.10 (Reflujo): el encabezado NO puede desbordar a 320 px ───────
   Las acciones son tres botones de tema (44 px cada uno) mas dos de idioma:
   240 px. Junto a la marca no caben en 320 px, y el resultado era SCROLL
   HORIZONTAL en las diez pantallas — incumplimiento de nivel AA, y contractual.

   ⚠️ LO INTRODUJO ESTILOS-002, o sea yo, el mismo dia. La tarea AVISABA del
   limite de 320 px —esta escrito en el comentario del selector de idioma, que
   ya habia peleado por ese espacio— y agregue 148 px de controles al lado sin
   volver a medir el total. No lo vio nadie: axe no marca reflujo, el build no
   sabe de anchos, y en el navegador de escritorio se ve perfecto.

   La salida NO es encoger los botones: 44 px es el area tactil, y recortarla
   cambia un incumplimiento por otro. Lo que se hace es dejar que la barra
   ENVUELVA, y las acciones bajan a su propia linea cuando no caben.

   ⚠️ `flex-wrap` en la barra no basta por si solo: la marca lleva un descriptor
   largo que la mantiene ancha. Se le permite encogerse para que el salto ocurra
   donde tiene que ocurrir. */
.encabezado__barra { flex-wrap: wrap; row-gap: var(--e-2); }

.encabezado__acciones { flex-wrap: wrap; }

@media (max-width: 26rem) {
    /* Las acciones ocupan la fila entera y se alinean a la izquierda, donde
       empieza a leerse todo lo demas. */
    .encabezado__acciones { width: 100%; justify-content: flex-start; }
}

/* ==========================================================================
   PIE
   ========================================================================== */
.pie {
    margin-top: auto;
    padding-block: var(--e-5);
    background: var(--superficie-alta);
    border-top: 1px solid var(--borde-sutil);
    font-size: var(--txt-sm);
    color: var(--texto-secundario);
}
.pie__contenido {
    display: flex;
    flex-direction: column;
    gap: var(--e-3);
    align-items: flex-start;
}
.pie__enlaces { display: flex; flex-wrap: wrap; gap: var(--e-4); }
.pie__enlaces a { color: var(--texto-secundario); }
@media (min-width: 48rem) {
    .pie__contenido { flex-direction: row; justify-content: space-between; align-items: center; }
}

/* ==========================================================================
   BOTONES
   ==========================================================================
   🔴 background-color usa la variante *-superficie y el texto usa
   --sobre-*. Con el token del acento de fondo, el blanco encima cae por
   debajo de 4.5:1 y la etiqueta deja de leerse. Ver tokens.css.
   ========================================================================== */
.boton {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--e-2);
    min-height: var(--tactil-min);
    padding: var(--e-3) var(--e-5);
    border: 1px solid transparent;
    border-radius: var(--r-md);
    font: inherit;
    font-weight: 600;
    line-height: 1.2;
    text-decoration: none;
    cursor: pointer;
    transition: background-color .15s ease, border-color .15s ease;
}
.boton:disabled, .boton[aria-disabled="true"] { opacity: .55; cursor: not-allowed; }

.boton--primario {
    background: var(--primario-superficie);
    color: var(--sobre-primario);
}
.boton--primario:hover:not(:disabled) { background: var(--primario-oscuro); }
.boton--primario:active:not(:disabled) { background: var(--primario-oscuro); }

.boton--secundario {
    background: var(--secundario-superficie);
    color: var(--sobre-secundario);
}

/* El contorno usa --borde-control y no --borde-sutil: es un boton, y su borde
   es lo unico que lo delimita (WCAG 1.4.11). */
.boton--contorno {
    background: transparent;
    color: var(--primario);
    border-color: var(--borde-control);
}
.boton--contorno:hover:not(:disabled) { background: var(--superficie-alta); }

.boton--texto {
    background: transparent;
    color: var(--primario);
    padding-inline: var(--e-3);
}
.boton--texto:hover:not(:disabled) { background: var(--superficie-alta); }

.boton--bloque { width: 100%; }

/* ==========================================================================
   TARJETAS
   ========================================================================== */
.tarjeta {
    background: var(--superficie);
    border: 1px solid var(--borde-sutil);
    border-radius: var(--r-lg);
    padding: var(--e-5);
    box-shadow: var(--sombra-1);
}
@media (min-width: 48rem) { .tarjeta { padding: var(--e-6); } }

/* Contenedor de las pantallas de una sola columna: acceso, registro,
   recuperacion, verificacion. */
.panel-centrado {
    width: 100%;
    max-width: var(--ancho-formulario);
    margin-inline: auto;
}

/* Las pantallas de rutas en escritorio (RUT-PC-002, Miguel, 3 oct 2026).
   Usaban .panel-centrado, que es el ancho del acceso y el registro (27rem), y
   en un monitor dejaban la mitad de la pantalla vacía. Aquí el límite es el del
   contenido, y lo que se reparte en columnas lo decide .puntos-ruta. */
.panel-ruta {
    width: 100%;
    max-width: 60rem;
}

/* Origen y destino lado a lado cuando caben, uno sobre otro cuando no.
   🔴 min(100%, 22rem) y no 22rem a secas: con el número solo, a 320 px la
   columna no cabe y la página gana desplazamiento horizontal (WCAG 1.4.10).
   La rejilla no cambia el orden del DOM: el foco y el lector siguen yendo
   origen → destino. */
.puntos-ruta {
    display: grid;
    gap: 0 var(--e-4);
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 22rem), 1fr));
    align-items: start;
}

/* Rejilla que se adapta sola, sin puntos de quiebre: cada tarjeta pide 16rem y
   el navegador decide cuantas caben. Es lo que hace que funcione igual en un
   telefono estrecho y en un monitor ancho sin escribir tres medias consultas. */
.rejilla-tarjetas {
    display: grid;
    gap: var(--e-4);
    grid-template-columns: repeat(auto-fit, minmax(min(16rem, 100%), 1fr));
}

/* ==========================================================================
   FORMULARIOS
   ==========================================================================
   Estructura esperada de cada campo:

       <div class="campo">
         <label class="campo__etiqueta" for="correo">
           Correo electronico <span class="campo__requerido">*</span>
         </label>
         <input class="campo__control" id="correo" aria-describedby="correo-error" />
         <p class="campo__error" id="correo-error" role="alert">…</p>
       </div>

   🔴 El <label> lleva `for` y el control su `id`. Un rotulo suelto al lado de
   un campo se ve identico y el lector de pantalla anuncia "cuadro de edicion"
   sin decir de que. Y el mensaje de error se enlaza con aria-describedby: sin
   eso, quien no ve la pantalla oye el campo pero nunca el motivo del rechazo
   —que es el criterio de HU-PC-104 de que los errores se ANUNCIEN a la ayuda
   tecnica y no solo se pinten.
   ========================================================================== */
.campo { margin-bottom: var(--e-4); }

.campo__etiqueta {
    display: block;
    margin-bottom: var(--e-2);
    font-size: var(--txt-sm);
    font-weight: 600;
    color: var(--texto);
}

/* 🔴 El asterisco NO puede ser la unica senal de obligatorio: es color y forma,
   no texto. Lleva su equivalente para lectores de pantalla, y ademas cada
   control obligatorio declara `required`, que es lo que de verdad anuncia la
   ayuda tecnica. HU-PC-104: "los campos obligatorios se identifican
   claramente". */
.campo__requerido { color: var(--error); font-weight: 700; }

.campo__control {
    display: block;
    width: 100%;
    min-height: var(--tactil-min);
    padding: var(--e-3);
    font: inherit;
    color: var(--texto);
    background: var(--superficie);
    border: 1px solid var(--borde-control);
    border-radius: var(--r-md);
    transition: border-color .15s ease, box-shadow .15s ease;
}
.campo__control::placeholder { color: var(--texto-secundario); opacity: 1; }

/* ⚠️ El marcador de posicion NUNCA sustituye al rotulo: desaparece al escribir
   y quien se distrae a media captura ya no sabe que iba en ese campo. */

.campo__control:hover:not(:disabled) { border-color: var(--primario); }
.campo__control:disabled { background: var(--superficie-alta); opacity: .7; }

.campo__control[aria-invalid="true"] {
    border-color: var(--error);
    border-width: 2px;
}

.campo__ayuda {
    margin: var(--e-2) 0 0;
    font-size: var(--txt-sm);
    color: var(--texto-secundario);
}

/* El error se marca con color, con icono Y con texto: tres canales, porque
   WCAG 1.4.1 prohibe que el color sea el unico. */
.campo__error {
    display: flex;
    align-items: flex-start;
    gap: var(--e-2);
    margin: var(--e-2) 0 0;
    font-size: var(--txt-sm);
    font-weight: 600;
    color: var(--error);
}
.campo__error::before { content: "\26A0"; flex: none; }

/* Campo de contrasena con el control de visibilidad dentro (CA-LOGIN-003:
   oculta por omision, con control para mostrarla). */
.campo__grupo { position: relative; display: flex; }
.campo__grupo .campo__control { padding-inline-end: calc(var(--tactil-min) + var(--e-2)); }
.campo__ojo {
    position: absolute;
    inset-block: 0;
    inset-inline-end: 0;
    width: var(--tactil-min);
    display: grid;
    place-items: center;
    background: none;
    border: 0;
    color: var(--texto-secundario);
    cursor: pointer;
    border-radius: var(--r-md);
}
.campo__ojo:hover { color: var(--primario); }

.campo--casilla {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: var(--e-3);
    align-items: start;
}

/* 🔴 24px, NO 20. La auditoría de HU-PC-105 lo midió: la casilla salía a 20px,
   por debajo del mínimo de área de destino. En el teléfono eso es un control
   que se falla al tocar — y esta casilla es obligatoria para registrarse, así
   que fallarla bloquea el alta.

   ⚠️ La regla general de base.css impone 44px a los controles, pero esta la
   exceptúa con min-height: 0 porque una casilla de 44×44 se ve enorme al lado
   de su texto. 24px es el mínimo real, y el resto del área de toque lo aporta
   el <label>, que está asociado y también activa la casilla. */
.campo--casilla input {
    width: 1.5rem;
    height: 1.5rem;
    min-height: 0;
    margin-top: .1rem;
    accent-color: var(--primario-superficie);
}

.campo--casilla label { font-size: var(--txt-sm); font-weight: 400; }

/* El enlace al documento va en su propio renglón dentro de la etiqueta, así que
   se lee como un control aparte y no como texto corrido: necesita su propia
   área de toque. Sin esto medía 17px. */
.campo--casilla label a {
    display: inline-block;
    padding-block: var(--e-1);
    min-height: 1.5rem;
}

/* ==========================================================================
   AVISOS
   ==========================================================================
   Los mensajes MSG del PRD. El contenedor lleva role="alert" cuando aparece
   como consecuencia de una accion —eso es lo que hace que el lector de
   pantalla lo lea sin que la persona vaya a buscarlo— y role="status" cuando
   es informativo y no urgente.

   🔴 Cada variante lleva su icono ademas del color, por WCAG 1.4.1. Un aviso
   que solo se distingue por ser rojo o verde no comunica nada en escala de
   grises ni para quien no distingue esos dos colores — que es el tipo de
   daltonismo mas comun.
   ========================================================================== */
.aviso {
    display: flex;
    align-items: flex-start;
    gap: var(--e-3);
    padding: var(--e-4);
    margin-bottom: var(--e-4);
    border: 1px solid;
    border-inline-start-width: 4px;
    border-radius: var(--r-md);
    font-size: var(--txt-sm);
}
.aviso__icono { flex: none; font-size: var(--txt-lg); line-height: 1.2; }
.aviso__texto { margin: 0; }

.aviso--error   { background: var(--error-fondo); border-color: var(--error);   color: var(--texto); }
.aviso--exito   { background: var(--exito-fondo); border-color: var(--exito);   color: var(--texto); }
.aviso--info    { background: var(--superficie-alta); border-color: var(--primario); color: var(--texto); }
.aviso--atencion{ background: var(--aviso-fondo); border-color: var(--aviso);   color: var(--texto); }

/* ==========================================================================
   SELECTOR DE IDIOMA (ADR-0009)
   ==========================================================================
   Es un <form> con botones de envio y no un <select>: cambiar el idioma tiene
   que escribir una cookie en la respuesta HTTP, y desde un componente
   interactivo la respuesta ya termino. Ver la nota de LayoutPublico.razor.
   ========================================================================== */
.idioma { display: flex; align-items: center; gap: var(--e-1); }
.idioma__opcion {
    min-height: var(--tactil-min);
    min-width: var(--tactil-min);
    padding-inline: var(--e-2);
    background: none;
    border: 1px solid transparent;
    border-radius: var(--r-sm);
    font: inherit;
    font-size: var(--txt-sm);
    font-weight: 600;
    color: var(--texto-secundario);
    cursor: pointer;
    text-transform: uppercase;
}
.idioma__opcion:hover { background: var(--superficie-alta); color: var(--texto); }

/* 🔴 El idioma activo se marca con aria-current, y el estilo cuelga de ese
   atributo y no de una clase. Asi es imposible pintarlo sin anunciarlo: quien
   navega con lector de pantalla oye "actual" en vez de adivinar por el color.
   Ademas del color lleva subrayado, por WCAG 1.4.1. */
.idioma__opcion[aria-current="true"] {
    color: var(--primario);
    border-color: var(--borde-control);
    text-decoration: underline;
    text-underline-offset: .2em;
}

/* ── Los dos rotulos del boton de idioma ─────────────────────────────────
   🔴 En telefono se VE el codigo corto y se OYE el nombre completo. A 320 px
   "ESPAÑOL ENGLISH" desborda el encabezado y corta la segunda opcion — medido.

   El nombre completo se oculta con la tecnica de .solo-lectores y NO con
   display:none: display:none lo sacaria tambien del arbol de accesibilidad y
   el boton se quedaria anunciado como "ES", que no le dice nada a quien no ve
   la pantalla. Ver la nota larga de SelectorIdioma.razor. */
.idioma__largo {
    position: absolute;
    width: 1px; height: 1px;
    padding: 0; margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}

/* ── Selector de tema claro/oscuro (ESTILOS-002) ─────────────────────────
   Calca al de idioma a proposito: viven pegados en el encabezado y dos
   controles del mismo grupo con alturas o focos distintos se leen como un
   error de maquetacion.

   🔴 Los tres botones van SOLO con icono, sin rotulo visible, y no es por
   estetica: a 320 px el encabezado ya lleva la marca y dos opciones de idioma.
   El nombre viaja en aria-label y en un span .solo-lectores, asi que a la
   ayuda tecnica no le falta nada — es la misma solucion que los dos rotulos
   del idioma, con el corte en el otro extremo. */
.tema { display: flex; align-items: center; gap: var(--e-1); }

.tema__opcion {
    /* 🔴 44px es el minimo tactil de WCAG 2.5.5, y aqui manda sobre el tamano
       del icono: el <svg> mide 20px y el area que se toca, no. */
    min-height: var(--tactil-min);
    min-width: var(--tactil-min);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: none;
    border: 1px solid transparent;
    border-radius: var(--r-sm);
    color: var(--texto-secundario);
    cursor: pointer;
}

.tema__opcion:hover { background: var(--superficie-alta); color: var(--texto); }

/* El icono hereda el color del boton — por eso el <path> lleva
   fill="currentColor" y no un color escrito. Sin eso, el icono se quedaria del
   mismo tinte en los dos temas, que es justo lo que este control cambia. */
.tema__icono { display: block; }

/* 🔴 Igual que en el idioma, el estado activo cuelga de aria-current y NO de
   una clase: asi es imposible pintarlo sin anunciarlo. Y lleva contorno ademas
   del color, por WCAG 1.4.1 — entre un sol tenue y un sol acentuado, el color
   seria la unica diferencia. */
.tema__opcion[aria-current="true"] {
    color: var(--primario);
    border-color: var(--borde-control);
    background: var(--superficie-alta);
}

@media (min-width: 30rem) {
    /* Ya cabe el nombre completo: se muestra ese y se retira el codigo. */
    .idioma__largo {
        position: static;
        width: auto; height: auto;
        margin: 0;
        overflow: visible;
        clip-path: none;
    }
    .idioma__corto { display: none; }
}

/* ==========================================================================
   PORTADA
   ========================================================================== */
.portada { padding-block: var(--e-6) var(--e-5); }
.portada__titulo { margin-bottom: var(--e-3); }
.portada__entrada {
    font-size: var(--txt-lg);
    color: var(--texto-secundario);
    max-width: 44rem;
    margin-bottom: var(--e-5);
}
.portada__acciones { display: flex; flex-wrap: wrap; gap: var(--e-3); }

/* Cada tarjeta de modulo es un enlace completo, no un titulo enlazable dentro
   de una caja: en el telefono el area de toque es la tarjeta entera. */
.modulo {
    display: block;
    padding: var(--e-5);
    background: var(--superficie);
    border: 1px solid var(--borde-sutil);
    border-radius: var(--r-lg);
    text-decoration: none;
    color: var(--texto);
    box-shadow: var(--sombra-1);
    transition: border-color .15s ease, box-shadow .15s ease;
}
.modulo:hover { border-color: var(--primario); box-shadow: var(--sombra-2); }
.modulo__titulo { color: var(--primario); margin-bottom: var(--e-2); }
.modulo__texto { color: var(--texto-secundario); font-size: var(--txt-sm); margin: 0; }

/* ==========================================================================
   REQUISITOS DE CONTRASEÑA
   ==========================================================================
   La lista se genera de PoliticaPassword — ver RequisitosPassword.razor. Se
   muestra ANTES de escribir, no solo al fallar: un requisito que solo aparece
   como error convierte el alta en adivinanza.
   ========================================================================== */
.requisitos {
    margin-bottom: var(--e-4);
    padding: var(--e-3) var(--e-4);
    background: var(--superficie-alta);
    border-radius: var(--r-md);
    font-size: var(--txt-sm);
}
.requisitos__titulo {
    margin: 0 0 var(--e-2);
    font-weight: 600;
    color: var(--texto);
}
.requisitos__lista {
    margin: 0;
    padding-inline-start: var(--e-5);
    color: var(--texto-secundario);
}
.requisitos__lista li { margin-bottom: var(--e-1); }
.requisitos__lista li:last-child { margin-bottom: 0; }

/* ==========================================================================
   AVISO DE RECONEXIÓN DEL CIRCUITO                            (HU-PC-103)
   ==========================================================================
   🔴 LAS CLASES components-reconnect-* LAS PONE BLAZOR, no el marcado. El
   framework busca #components-reconnect-modal y le añade una de ellas según el
   estado del circuito. Renombrarlas no rompe la compilación: el aviso
   simplemente deja de aparecer — o aparece siempre.

   Por omisión está oculto. Cada estado enciende su propio párrafo, y por eso
   los tres se declaran ocultos y se muestran uno a uno.
   ========================================================================== */
.reconexion {
    display: none;
    position: fixed;
    inset-inline: 0;
    top: 0;
    z-index: 2000;
    padding: var(--e-3) var(--e-4);
    background: var(--aviso-fondo);
    color: var(--texto);
    border-bottom: 3px solid var(--aviso);
    box-shadow: var(--sombra-2);
}

/* Los tres estados en que Blazor lo hace visible. */
.reconexion.components-reconnect-show,
.reconexion.components-reconnect-failed,
.reconexion.components-reconnect-rejected { display: block; }

/* Y el que lo apaga cuando el circuito vuelve. */
.reconexion.components-reconnect-hide { display: none; }

.reconexion__caja {
    max-width: var(--ancho-contenido);
    margin-inline: auto;
    font-size: var(--txt-sm);
}
.reconexion__caja p {
    display: none;
    margin: 0;
    align-items: center;
    gap: var(--e-2);
    flex-wrap: wrap;
}

/* Reintentando. */
.components-reconnect-show .reconexion__intentando { display: flex; }

/* Recuperada: se anuncia un instante antes de que Blazor oculte el aviso. */
.components-reconnect-hide .reconexion__recuperada { display: flex; }

/* Se agotaron los reintentos, o el servidor ya no reconoce el circuito. En los
   dos casos el remedio es el mismo, así que comparten párrafo. */
.components-reconnect-failed .reconexion__fallida,
.components-reconnect-rejected .reconexion__fallida { display: flex; }

.reconexion__nota { color: var(--texto-secundario); }

.reconexion__fallida .boton { margin-inline-start: var(--e-3); }

/* ⚠️ El giro se apaga con prefers-reduced-motion — ver base.css. Un indicador
   que gira sin parar es de los que más molestan a quien tiene trastorno
   vestibular, y este aparece justo cuando la persona está esperando. */
.components-reconnect-show .reconexion__intentando > span[aria-hidden] {
    display: inline-block;
    animation: reconexion-giro 1.2s linear infinite;
}

@keyframes reconexion-giro {
    to { transform: rotate(360deg); }
}

/* ==========================================================================
   SHELL DEL CIUDADANO AUTENTICADO                             (HU-PC-301)
   ==========================================================================
   Encabezado arriba, menú a la izquierda, contenido a la derecha. En teléfono
   el menú se colapsa a un <details> y el contenido pasa debajo.
   ========================================================================== */
.marco {
    display: flex;
    flex-direction: column;
    min-height: 100svh;
}

.encabezado__barra--ancho {
    width: 100%;
    max-width: none;
    padding-inline: var(--e-4);
    margin-inline: 0;
}

/* ── La información del ciudadano en el encabezado (6.4.2) ───────────────── */
.usuario {
    display: flex;
    flex-direction: column;
    line-height: 1.2;
    text-align: end;
}
.usuario__nombre { font-weight: 600; font-size: var(--txt-sm); }
.usuario__correo { font-size: var(--txt-xs); color: var(--texto-secundario); }

/* 🔴 En teléfono se oculta CON LA TÉCNICA DE .solo-lectores, no con
   display:none. Así sigue en el árbol de accesibilidad: quien usa lector de
   pantalla necesita saber con qué cuenta está dentro tanto como quien lo lee en
   la esquina — y más, porque no puede mirar para comprobarlo. */
@media (max-width: 47.99rem) {
    .usuario {
        position: absolute;
        width: 1px; height: 1px;
        padding: 0; margin: -1px;
        overflow: hidden;
        clip-path: inset(50%);
        white-space: nowrap;
    }
}

.marco__cuerpo {
    flex: 1 0 auto;
    display: flex;
    flex-direction: column;
}

/* ==========================================================================
   EL MENÚ
   ==========================================================================
   ⚠️ Es un <details>. En teléfono funciona como el navegador manda: el
   <summary> abre y cierra. En escritorio se fuerza abierto, porque el criterio
   dice que "permanece visible durante toda la sesión" y en pantalla ancha un
   menú que hay que abrir cada vez no lo cumple.
   ========================================================================== */
.menu {
    background: var(--superficie);
    border-bottom: 1px solid var(--borde-sutil);
}

.menu__boton {
    display: flex;
    align-items: center;
    gap: var(--e-2);
    min-height: var(--tactil-min);
    padding: var(--e-3) var(--e-4);
    font-weight: 600;
    cursor: pointer;
    list-style: none;   /* quita el triángulo por omisión de algunos motores */
}
/* Safari necesita este pseudo-elemento aparte para quitar el marcador. */
.menu__boton::-webkit-details-marker { display: none; }

/* El triángulo se dibuja aquí para poder girarlo al abrir: es una señal de
   estado ADEMÁS del aria-expanded que <details> ya anuncia solo. */
.menu__boton::before {
    content: "▸";
    transition: transform .15s ease;
}
.menu[open] > .menu__boton::before { transform: rotate(90deg); }

.menu__lista {
    display: flex;
    flex-direction: column;
    padding: var(--e-2) var(--e-3) var(--e-4);
    gap: var(--e-1);
}

.modulo-enlace {
    display: flex;
    align-items: center;
    gap: var(--e-3);
    min-height: var(--tactil-min);
    padding: var(--e-2) var(--e-3);
    border-radius: var(--r-md);
    color: var(--texto);
    text-decoration: none;
    /* El borde existe siempre y en transparente: si solo apareciera en el
       activo, el texto se movería tres píxeles al cambiar de módulo. */
    border-inline-start: 3px solid transparent;
}
.modulo-enlace:hover { background: var(--superficie-alta); }

.modulo-enlace__icono { flex: none; width: 1.25rem; text-align: center; }

/* 🔴 El módulo activo se marca por TRES canales: color, barra lateral y
   aria-current. El estilo cuelga de aria-current y NO de la clase que agrega
   NavLink, así que es imposible pintarlo sin anunciarlo — que es justo el
   descuido habitual (WCAG 1.4.1 y 4.1.2). */
.modulo-enlace[aria-current="page"] {
    background: var(--superficie-alta);
    border-inline-start-color: var(--primario);
    color: var(--primario);
    font-weight: 600;
}

/* Cerrar sesión, separado del grupo de módulos. */
.modulo-enlace--aparte {
    margin-top: var(--e-3);
    padding-top: var(--e-3);
    border-top: 1px solid var(--borde-sutil);
    border-start-start-radius: 0;
    border-start-end-radius: 0;
}

.marco__contenido {
    flex: 1 1 auto;
    min-width: 0;   /* sin esto, un contenido ancho desborda la rejilla */
    padding: var(--e-5) var(--e-4) var(--e-7);
}

/* ==========================================================================
   ESCRITORIO: el menú deja de ser desplegable
   ==========================================================================
   🔴 Se fuerza visible el contenido del <details> y se oculta el <summary>.
   Verificado en el navegador: sin la regla del contenido, en escritorio el menú
   sale cerrado y sin forma de abrirlo, porque el summary ya no está.
   ========================================================================== */
@media (min-width: 64rem) {
    .marco__cuerpo {
        flex-direction: row;
        align-items: flex-start;
    }

    .menu {
        flex: 0 0 16rem;
        position: sticky;
        top: var(--alto-encabezado);
        align-self: stretch;
        border-bottom: 0;
        border-inline-end: 1px solid var(--borde-sutil);
        min-height: calc(100svh - var(--alto-encabezado));
    }

    /* ⚠️ El <summary> se oculta, pero eso NO abre el menu: el atributo open lo
       pone el marcado y lo mantiene menu.js. Ocultar el summary sin garantizar
       el open dejaba el menu de escritorio VACIO y sin forma de abrirlo —
       comprobado en pantalla. */
    .menu__boton { display: none; }

    .marco__contenido {
        padding: var(--e-6) var(--e-6) var(--e-8);
        max-width: var(--ancho-contenido);
    }
}

/* ==========================================================================
   DOCUMENTOS LEGALES                                          (HU-PC-604)
   ==========================================================================
   Aviso de Privacidad y Términos. Texto largo, así que la medida de línea se
   acota: más de unos 75 caracteres y el ojo pierde el renglón al saltar — y en
   un documento que hay que leer entero, eso se nota.
   ========================================================================== */
.documento { max-width: 46rem; }

.documento__version {
    margin: calc(-1 * var(--e-2)) 0 var(--e-5);
    font-size: var(--txt-sm);
    color: var(--texto-secundario);
}

.documento__cuerpo { margin-bottom: var(--e-6); }
.documento__cuerpo h2 { margin-top: var(--e-6); }
.documento__cuerpo h2:first-child { margin-top: 0; }
.documento__cuerpo h3 { margin-top: var(--e-5); }
.documento__cuerpo ul,
.documento__cuerpo ol { margin: 0 0 var(--e-4); padding-inline-start: var(--e-5); }
.documento__cuerpo li { margin-bottom: var(--e-2); }

/* ── Rutas de Interés (HU-PC-401) ─────────────────────────────────────────── */

/* 🔴 list-style: none NO basta en Safari/VoiceOver: al quitar los bullets deja
   de anunciarla como lista, y con ella se pierde el "lista de 4 elementos" que
   es justo lo que hace navegable el listado sin ver la pantalla. role="list"
   lo repone; ver ListadoDeRutas.razor. */
.lista-rutas {
    list-style: none;
    padding: 0;
    /* El margen de arriba separa la primera tarjeta del botón "Crear ruta",
       que va justo encima y salía pegado (Miguel, recorrido del 3 oct 2026). */
    margin: var(--e-4) 0 0;
    display: grid;
    gap: var(--e-4);
}

.ruta__nombre {
    margin: 0 0 var(--e-2);
    font-size: var(--txt-lg);
}

.ruta__avisos {
    margin: 0 0 var(--e-2);
    font-size: var(--txt-sm);
    color: var(--texto-secundario);
    display: flex;
    align-items: center;
    gap: var(--e-2);
}

/* El color es un refuerzo, nunca el único medio: el texto de al lado ya dice
   si los avisos están activados (WCAG 1.4.1). */
.ruta__avisos--si { color: var(--primario); }

.ruta__datos {
    margin: 0;
    font-size: var(--txt-sm);
    color: var(--texto-secundario);
}

/* El interruptor de avisos de cada ruta (HU-PC-404).

   ⚠️ min-height se queda en los 44px que impone base.css a los controles: es
   area de destino tactil (WCAG 2.5.5/2.5.8) y "chico" aquí es el ancho y la
   tipografía, nunca el area que hay que acertar con el dedo. */
.ruta__interruptor { margin-top: var(--e-3); }

.boton--chico {
    font-size: var(--txt-sm);
    padding-inline: var(--e-4);
}

/* El interruptor y el enlace de editar comparten renglón. */
.ruta__interruptor {
    display: flex;
    flex-wrap: wrap;
    gap: var(--e-3);
    align-items: center;
}

/* Acción destructiva (HU-PC-403).

   🔴 CONTORNO Y NO RELLENO, y no es estética. Reutiliza --error, que es el
   único rojo medido de la paleta: 6.17:1 sobre el fondo claro y 10.66:1 sobre
   el oscuro. Un botón RELLENO exigiría texto blanco encima, y en tema oscuro
   --error es un salmón claro (#ffb4ab) sobre el que el blanco no contrasta —o
   sea que habría que inventar un segundo rojo y medirlo, para un botón que
   además no debe parecer la acción principal.

   ⚠️ El rojo no es el único aviso: el texto dice "Eliminar" y la confirmación
   explica qué se pierde (WCAG 1.4.1). El color solo lo refuerza. */
.boton--peligro {
    background: transparent;
    color: var(--error);
    border: 1px solid var(--error);
}

.boton--peligro:hover { background: var(--error-fondo); }

/* Los datos del recorrido, de consulta. */
.datos-ruta {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: var(--e-2) var(--e-4);
    margin: 0 0 var(--e-4);
    font-size: var(--txt-sm);
}

.datos-ruta dt { font-weight: 600; }
.datos-ruta dd { margin: 0; color: var(--texto-secundario); }

/* Captura de un extremo de la ruta (HU-PC-402). */
.captura-punto {
    border: 1px solid var(--borde-sutil);
    border-radius: var(--r-md);
    padding: var(--e-4);
    margin: 0 0 var(--e-4);
}

.captura-punto legend {
    font-weight: 600;
    padding-inline: var(--e-2);
}

/* RUT-PC-001: la dirección ya no va pegada debajo del campo de coordenadas
   —que ahora está plegado al final—, sino después de las instrucciones del
   mapa. El margen negativo que la subía hacia el campo la encimaba. */
.captura-punto__direccion {
    margin: var(--e-2) 0 var(--e-3);
    font-size: var(--txt-sm);
    color: var(--texto-secundario);
}

/* El aviso de cobertura en vivo. Vacío no ocupa lugar; con texto, el color de
   error de los campos, que ya está medido contra el fondo (docs/estilos.md). */
.captura-punto__cobertura {
    margin: 0 0 var(--e-3);
    font-size: var(--txt-sm);
    color: var(--error);
    font-weight: 600;
}
.captura-punto__cobertura:empty { display: none; }

/* Las coordenadas escritas, plegadas (RUT-PC-001). El <summary> es el control:
   lleva el área táctil mínima porque es lo único que se pulsa. */
.captura-punto__coordenadas { margin: var(--e-3) 0 0; }
.captura-punto__coordenadas > summary {
    cursor: pointer;
    min-height: var(--tactil-min);
    display: flex;
    align-items: center;
    font-size: var(--txt-sm);
    font-weight: 600;
}

.mapa__mi-ubicacion-caja { margin: 0 0 var(--e-3); }
.mapa__mi-ubicacion-caja .mapa__aviso:empty { display: none; }

/* El resultado de Calcular, con la vista previa. */
.calculo-ruta { margin: var(--e-4) 0 0; }
.calculo-ruta h2 { margin-top: 0; }

/* ==========================================================================
   EL MAPA                                     (HU-PC-402, HU-PC-405)
   ==========================================================================
   ⚠️ Estas reglas visten lo que hay ALREDEDOR del mapa. El mapa en sí lo pinta
   MapLibre sobre un <canvas>, y sus capas no son elementos del DOM: el color
   del trayecto y el del pin se declaran en mapa.js, no aquí. Buscar el color
   de la línea en esta hoja es una pérdida de tiempo garantizada. */

.captura-punto__ayuda,
.mapa__aviso {
    margin: var(--e-2) 0 0;
    font-size: var(--txt-sm);
    color: var(--texto-secundario);
}

.mapa__buscador { margin: 0 0 var(--e-3); }

.mapa__sugerencias {
    list-style: none;
    margin: var(--e-2) 0 0;
    padding: 0;
    border: 1px solid var(--borde-sutil);
    border-radius: var(--r-sm);
    background: var(--superficie);
    box-shadow: var(--sombra-1);
    max-height: 15rem;
    overflow-y: auto;
}

.mapa__sugerencia {
    display: block;
    width: 100%;
    text-align: start;
    /* 🔴 El alto mínimo es el área táctil de WCAG 2.5.5. Una lista de
       sugerencias con renglones de 30 px es de lo más difícil de acertar con el
       pulgar en movimiento, que es justo cuando se usa. */
    min-height: var(--tactil-min);
    padding: var(--e-2) var(--e-3);
    border: 0;
    background: transparent;
    color: var(--texto);
    font: inherit;
    cursor: pointer;
}

.mapa__sugerencia:hover { background: var(--superficie-alta); }

.mapa__lienzo {
    height: 18rem;
    border: 1px solid var(--borde-sutil);
    border-radius: var(--r-md);
    overflow: hidden;
}

/* El recorrido completo necesita más alto: en 18 rem una ruta de punta a punta
   de la ciudad se ve como una línea sin contexto. */
.mapa__lienzo--ruta { height: 24rem; }

.mapa-ruta { margin: 0 0 var(--e-5); }

/* Inicio y fin del recorrido (RUT-PC-003), iguales a los de la app. El color
   lo pone mapa.js en línea, porque es el dato: teal el origen, naranja el
   destino. */
.mapa__extremo {
    width: 26px;
    height: 26px;
    border-radius: 50%;
    border: 3px solid #fff;
    box-shadow: 0 1px 4px rgba(0, 0, 0, .4);
}

/* El icono de una cámara sobre el mapa.
   🔴 Es un <button> de verdad (ver mapa.js): entra en la tabulación y responde
   a Intro. Un icono sobre un mapa es lo más fácil de dejar inoperable con
   teclado, y WCAG 2.1 AA es contractual. */
.mapa__camara {
    width: var(--tactil-min);
    height: var(--tactil-min);
    display: grid;
    place-items: center;
    border: 2px solid var(--sobre-primario);
    border-radius: var(--r-full);
    background: var(--primario-superficie);
    box-shadow: var(--sombra-1);
    font-size: var(--txt-md);
    line-height: 1;
    cursor: pointer;
}

.mapa__camara:focus-visible {
    outline: 3px solid var(--foco);
    outline-offset: 2px;
}

.mapa__ficha-nombre {
    margin: 0 0 var(--e-2);
    font-weight: 600;
    color: var(--texto);
}

.mapa__ficha-imagen {
    display: block;
    width: 100%;
    height: auto;
    border-radius: var(--r-sm);
    background: var(--superficie-alta);
}

.mapa__ficha-aviso {
    margin: 0;
    font-size: var(--txt-sm);
    color: var(--texto-secundario);
}

/* ⚠️ La ficha de MapLibre trae su propio blanco y su propia tipografía. Sin
   esto, sobre el tema oscuro sale una tarjeta blanca con texto claro dentro —
   ilegible— y con la letra del sistema en medio de una interfaz en Inter. */
.maplibregl-popup-content {
    background: var(--superficie);
    color: var(--texto);
    font-family: var(--fuente);
    border-radius: var(--r-md);
    box-shadow: var(--sombra-2);
}

.maplibregl-popup-tip { border-top-color: var(--superficie); }

.maplibregl-popup-close-button {
    /* Misma razón que el icono: es un control, y el de la librería mide 20 px. */
    min-width: var(--tactil-min);
    min-height: var(--tactil-min);
    color: var(--texto);
    font-size: var(--txt-lg);
}

/* Quien pidió menos movimiento no quiere que el mapa se deslice al centrarse.
   MapLibre anima con JavaScript, así que esto no lo apaga solo: mapa.js usa
   duration 0 al encuadrar y aquí se corta el resto. */
@media (prefers-reduced-motion: reduce) {
    .maplibregl-map * { transition: none !important; }
}

/* ==========================================================================
   CÁMARAS: catálogo y visor                    (HU-PC-501, HU-PC-502)
   ========================================================================== */

.visor-seleccion { margin: 0 0 var(--e-6); }

/* BUG-012 (14 sep 2026). Aqui decia `align-items: end` sobre un flex, y como
   Buscar lleva su texto de ayuda DEBAJO, Zona y Filtrar se alineaban al fondo
   de esa ayuda: sus etiquetas quedaban mas abajo que la de Buscar.

   🔴 Rejilla de tres filas —etiquetas, controles, ayuda— y cada .campo se
   reparte en ellas con `subgrid`. Asi la ayuda de Buscar ocupa SU fila sin
   empujar a nadie, y Filtrar se coloca en la fila de los controles con su
   misma altura. Decision de Miguel: los tres en la misma fila, etiquetas
   arriba. Con un margen calculado a ojo para bajar el boton, bastaria cambiar
   el tamano de letra de la etiqueta para volver a desalinearlo.

   En pantallas angostas se apilan, sin rejilla: a 320 px (reflujo, SC 1.4.10)
   tres columnas no caben. */
.catalogo-camaras__filtros {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: var(--e-3);
    margin: 0 0 var(--e-4);
}

.catalogo-camaras__filtros .campo { margin: 0; }
.catalogo-camaras__filtros > .boton { align-self: flex-start; }

@media (min-width: 40rem) {
    .catalogo-camaras__filtros {
        display: grid;
        grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) auto;
        grid-template-rows: auto auto auto;
        column-gap: var(--e-3);
        row-gap: 0;
        align-items: start;
    }

    .catalogo-camaras__filtros .campo {
        display: grid;
        grid-row: 1 / span 3;
        grid-template-rows: subgrid;
    }

    .catalogo-camaras__filtros > .boton {
        grid-row: 2;
        align-self: stretch;
    }
}

.lista-camaras,
.lista-visor {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    /* auto-fill y no un número de columnas: la cuadrícula se acomoda sola de un
       teléfono a un monitor sin un solo punto de corte que mantener. */
    grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr));
    gap: var(--e-3);
}

.camara { margin: 0; }

.camara__posicion {
    margin: 0 0 var(--e-1);
    font-size: var(--txt-xs);
    text-transform: uppercase;
    letter-spacing: .04em;
    color: var(--texto-secundario);
}

.camara__nombre {
    margin: 0 0 var(--e-1);
    font-size: var(--txt-md);
}

.camara__datos {
    margin: 0 0 var(--e-3);
    font-size: var(--txt-sm);
    color: var(--texto-secundario);
}

/* 🔴 Con TEXTO, no solo con un color atenuado: WCAG 1.4.1 dice que el color no
   puede ser el único medio, y "si una cámara deja de estar disponible se indica
   la condición" es criterio de aceptación. */
.camara__caida {
    margin: 0 0 var(--e-3);
    font-size: var(--txt-sm);
    color: var(--aviso);
    font-weight: 600;
}

.camara__elegida {
    margin: 0;
    font-size: var(--txt-sm);
    color: var(--primario);
    font-weight: 600;
}

.camara__acciones {
    display: flex;
    gap: var(--e-2);
    flex-wrap: wrap;
}

/* Cada acción es su propio <form>, y un form es block: sin esto, los tres
   botones de una tarjeta se apilan en tres renglones. */
.camara__acciones form { margin: 0; display: contents; }

/* Un control deshabilitado tiene que VERSE deshabilitado, y no basta con el
   atributo: quien mira la pantalla necesita saber por qué la flecha de la
   primera cámara no hace nada. */
.boton:disabled {
    opacity: .45;
    cursor: not-allowed;
}

/* ==========================================================================
   EL VISOR DE DIECISÉIS POSICIONES                        (HU-PC-503)
   ==========================================================================
   🔴 La cuadrícula "se adapta correctamente a distintas resoluciones" es
   criterio de aceptación, y por eso NO hay 4×4 fijo: en un teléfono, dieciséis
   columnas de 90 px son dieciséis imágenes que no se distinguen. auto-fit deja
   una columna en móvil y cuatro en escritorio sin un solo punto de corte que
   alguien tenga que mantener. */
.visor-rejilla {
    list-style: none;
    margin: var(--e-4) 0 0;
    padding: 0;
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
    gap: var(--e-3);
}

.visor-posicion {
    border: 1px solid var(--borde-sutil);
    border-radius: var(--r-md);
    background: var(--superficie);
    padding: var(--e-3);
}

/* Una posición libre se ve distinta de una ocupada, PERO también lo dice con
   texto: el color no puede ser el único medio (WCAG 1.4.1). */
.visor-posicion--vacia {
    background: var(--superficie-alta);
    border-style: dashed;
    display: grid;
    place-content: center;
    min-height: 12rem;
}

.visor-posicion__vacia {
    margin: 0;
    color: var(--texto-secundario);
    font-size: var(--txt-sm);
}

/* 🔴 aspect-ratio y no una altura fija: la caja reserva su sitio ANTES de que
   llegue la imagen, así que la cuadrícula no da un salto cuando cada posición
   termina de cargar. Con dieciséis cargando a ritmos distintos, sin esto la
   página brinca dieciséis veces. */
.visor-posicion__imagen {
    aspect-ratio: 4 / 3;
    background: var(--superficie-alta);
    border-radius: var(--r-sm);
    overflow: hidden;
    display: grid;
    place-items: center;
    margin: 0 0 var(--e-2);
}

.visor-posicion__imagen img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.visor-posicion__caida,
.visor-posicion__hora {
    margin: 0;
    font-size: var(--txt-sm);
}

.visor-posicion__caida {
    padding: var(--e-3);
    text-align: center;
    color: var(--aviso);
    font-weight: 600;
}

.visor-posicion__hora { color: var(--texto-secundario); }

.visor-posicion .camara__nombre { margin: 0 0 var(--e-1); }
.visor-posicion .camara__datos { margin: 0 0 var(--e-2); }

/* Actualizar el visor (HU-PC-504). */
.visor-acciones {
    display: flex;
    flex-wrap: wrap;
    gap: var(--e-2);
    align-items: center;
    margin: 0 0 var(--e-3);
}

.visor-acuse {
    margin: 0 0 var(--e-3);
    font-size: var(--txt-sm);
    color: var(--aviso);
}

/* El acuse vacío no debe ocupar sitio: con :empty, la rejilla no se mueve
   cuando aparece y desaparece el mensaje. */
.visor-acuse:empty { display: none; }

.visor-posicion [data-actualizar-una] { margin-top: var(--e-2); }
.visor-posicion [data-ampliar] { margin: var(--e-2) var(--e-2) 0 0; }

/* ───────────────────────────────────────────────────────────────────────────
   UNA CÁMARA EN GRANDE                                       (CAM-PC-FULL-001)
   ───────────────────────────────────────────────────────────────────────────
   🔴 COLORES FIJOS, NO TOKENS: el fondo es negro en los dos temas, igual que en
   la app (un marco claro alrededor de un fotograma de noche deslumbra). Por eso
   el contraste se midió contra #000:
     blanco ........................ 21:1
     #D0D0D0 (la hora) ............. 13.6:1
     #8A8A8A (borde de los botones)  6.1:1  (SC 1.4.11 pide 3:1)
   y el aviso usa los tokens --aviso / --aviso-fondo, que ya están medidos entre
   sí. Si alguien cambia el negro, hay que volver a medir los tres. */
.ampliada {
    width: 100vw;
    height: 100vh;
    height: 100dvh;
    max-width: none;
    max-height: none;
    margin: 0;
    padding: 0;
    border: 0;
    background: #000;
    color: #fff;
    overflow: hidden;
}

.ampliada[open] {
    display: grid;
    grid-template-rows: auto minmax(0, 1fr) auto auto;
}

.ampliada::backdrop { background: #000; }

/* 🔴 Las reglas de abajo ponen display a botones y contenedores, y eso le gana
   al atributo hidden del navegador: sin esto se ven a la vez la imagen y el
   aviso de cámara caída, y el botón Actualizar aparece donde no aplica. */
.ampliada [hidden] { display: none !important; }

.ampliada__barra {
    display: flex;
    justify-content: space-between;
    padding: var(--e-2);
}

/* 48 px: por encima del mínimo táctil de 44 (SC 2.5.5), como en la app. */
.ampliada__boton {
    display: inline-grid;
    place-items: center;
    min-width: 48px;
    min-height: 48px;
    padding: 0;
    border: 1px solid #8A8A8A;
    border-radius: var(--r-full);
    background: #262626;
    color: #fff;
    font: inherit;
    font-size: var(--txt-xl);
    font-weight: 700;
    line-height: 1;
    cursor: pointer;
}

.ampliada__boton:hover { background: #3a3a3a; }

/* El anillo del Portal es teal y no se ve sobre negro: aquí va blanco. */
.ampliada__boton:focus-visible {
    outline: 3px solid #fff;
    outline-offset: 2px;
}

/* En el extremo se ve atenuado, pero sigue siendo enfocable (ver Visor.razor). */
.ampliada__boton[aria-disabled="true"] { opacity: .45; cursor: default; }

/* 🔴 touch-action: none, o el navegador se queda con el pellizco y amplía la
   PÁGINA entera en vez de la imagen. */
.ampliada__escenario {
    position: relative;
    display: grid;
    /* 🔴 Una sola celda de tamaño DEFINIDO. Con filas automáticas el 100 % de
       max-height no tiene contra qué medirse, y en horizontal la imagen salía a
       su alto completo, cortada por abajo. */
    grid-template: minmax(0, 1fr) / minmax(0, 1fr);
    place-items: center;
    overflow: hidden;
    touch-action: none;
    min-height: 0;
}

/* max-* y no object-fit: así la caja del <img> ES la imagen, y los límites
   del arrastre se calculan con su tamaño real (sin franjas negras). */
.ampliada__imagen,
.ampliada__estado { grid-area: 1 / 1; }

.ampliada__imagen {
    max-width: 100%;
    max-height: 100%;
    transform-origin: center;
    user-select: none;
    -webkit-user-select: none;
    cursor: zoom-in;
}

.ampliada__imagen--ampliada { cursor: grab; }
.ampliada__imagen--arrastrando { cursor: grabbing; }

.ampliada__estado {
    display: grid;
    justify-items: center;
    gap: var(--e-3);
    padding: var(--e-5);
    text-align: center;
}

.ampliada__estado img { width: 6rem; height: auto; }
.ampliada__estado p { margin: 0; font-weight: 600; }

.ampliada__acuse {
    margin: 0 var(--e-4);
    padding: var(--e-1) var(--e-3);
    border-radius: var(--r-sm);
    background: var(--aviso-fondo);
    color: var(--aviso);
    font-size: var(--txt-sm);
}

.ampliada__acuse:empty { display: none; }

/* flex-wrap: a 320 px el nombre y los botones no caben en un renglón, y el
   zoom baja al siguiente en vez de empujar la página de lado (SC 1.4.10). */
.ampliada__pie {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--e-3);
    padding: var(--e-2) var(--e-4) var(--e-4);
}

.ampliada__titulo { min-width: 0; flex: 1 1 12rem; }

.ampliada__titulo h2 {
    margin: 0;
    font-size: var(--txt-md);
    color: #fff;
    overflow-wrap: anywhere;
}

.ampliada__titulo p {
    margin: 0;
    font-size: var(--txt-sm);
    color: #D0D0D0;
}

.ampliada__zoom {
    display: flex;
    align-items: center;
    gap: var(--e-2);
}

.ampliada__nivel {
    min-width: 3.5rem;
    text-align: center;
    font-size: var(--txt-sm);
    font-variant-numeric: tabular-nums;
}
