/* ════════════════════════════════════════════════════════════════════════════
   VENTA AMBULANTE · Municipalidad de Pinamar

   Los tokens --pin-* son un ESPEJO del tema Clásico de `pinamar-ui.css`
   (=PORTAL_PINAMAR=/public/css/pinamar-ui.css): mismo verde institucional
   #007242, misma tipografía (Fira Sans + DM Mono para datos duros) y la misma
   escala de radios 24/16/10/8. Se copian en vez de importar el archivo entero
   porque aquel trae el shell de panel con sidebar —30 KB que esta app nunca
   usa— y porque el mecanismo de copia de `version-assets.js` del portal apunta
   sólo a Pinamar Datos. Si en algún momento se quiere fuente única de verdad,
   hay que sumar esta carpeta a ese script (ver CLAUDE.md).

   🔴 SIN TEMA OSCURO, a propósito. Esta app se usa parada en la vereda de
   Pinamar en enero a las 3 de la tarde. Con sol directo, fondo claro y texto
   oscuro se lee y el inverso no. Es una decisión de legibilidad al aire libre,
   no un olvido.

   Reglas de oro heredadas del panel de Datos: nada de backdrop-filter en
   tarjetas, no animar box-shadow en bucle, y ningún transform en un contenedor
   que envuelva algo position:fixed.
   ════════════════════════════════════════════════════════════════════════════ */

:root {
  --pin-accent:        #007242;
  --pin-accent-soft:   #00925a;
  --pin-accent-dim:    rgba(0, 114, 66, .10);
  --pin-accent-ink:    #ffffff;

  --pin-bg:            #ffffff;
  --pin-bg-suave:      #f4f8f5;
  --pin-surface:       #ffffff;
  --pin-line:          rgba(0, 114, 66, .16);
  --pin-line-strong:   rgba(0, 114, 66, .45);

  --pin-text:          #333333;
  --pin-text-muted:    rgba(51, 51, 51, .62);
  --pin-text-faint:    rgba(51, 51, 51, .42);

  --pin-warn:          #b45309;
  --pin-danger:        #be123c;
  --pin-info:          #0369a1;
  --pin-ok:            #007242;

  --pin-font:          'Fira Sans', system-ui, -apple-system, 'Segoe UI', sans-serif;
  --pin-font-mono:     'DM Mono', ui-monospace, 'Cascadia Mono', monospace;

  /* ── La cuadrícula de la puerta ────────────────────────────────────────
     Van como tokens y no sueltas en el degradado para poder ajustar el verde
     en UN lugar: es el tipo de valor que se toca tres veces hasta encontrarlo.

     ⚰️ Hubo una segunda retícula más marcada cada 120 px, para que se leyera
        como papel milimetrado. En una maqueta chica se veía bien; a pantalla
        completa esas bandas dominan todo y la trama pasa de fondo a dibujo.
        Una sola retícula, fina y pareja.

     ⚠️ Y el PASO crece en pantallas anchas. Es lo que casi se me pasa: 24 px
        dan unas diez celdas en un teléfono —que es la densidad buena— y ochenta
        en un monitor. El mismo número absoluto produce dos fondos distintos, así
        que lo que hay que mantener parejo es la cantidad de celdas a la vista,
        no el tamaño de la celda. */
  /* ⚠️ Los puntos aguantan MÁS color que las líneas y lo necesitan: una línea
     cruza la pantalla entera y suma mucha tinta, un punto ocupa dos píxeles.
     Al 10 % —el valor que servía para la retícula— la trama desaparecía. */
  --pin-grid-fina:     rgba(0, 114, 66, .20);
  --pin-grid-paso:     22px;

  --pin-r-lg:          24px;
  --pin-r-md:          16px;
  --pin-r-sm:          10px;
  --pin-r-xs:          8px;
  --pin-r-pill:        999px;

  --pin-shadow-card:   0 6px 22px rgba(18, 53, 36, .07);
  --pin-shadow-pop:    0 18px 50px rgba(0, 100, 50, .16);

  --pin-ease:          cubic-bezier(.16, 1, .3, 1);
  --pin-t:             .28s;

  /* Alto de la barra inferior + el notch de los iPhone. Se usa como padding
     del contenido: sin esto, el último botón de cada pantalla queda debajo de
     la barra y no se puede tocar. */
  --barra-h:           calc(72px + env(safe-area-inset-bottom, 0px));
}

* { box-sizing: border-box; }

/* 🔴 Va antes que cualquier regla de componente y con !important.
   El `hidden` del HTML es sólo `display: none` con la especificidad más baja
   que existe, así que CUALQUIER regla propia que fije un display se lo come.
   Acá pasaba en seis lugares — el botón del obturador durante la secuencia de
   rostro, el cartel de REC, la cuenta regresiva, el contador de la cola y el
   engranaje de administración — que llevan display:flex y se mostraban igual
   con el atributo puesto. Mismo criterio que `styles.css` de Central Pinamar. */
[hidden] { display: none !important; }

html, body {
  margin: 0; padding: 0;
  min-height: 100%;
  background: var(--pin-bg);
  color: var(--pin-text);
  font-family: var(--pin-font);
  font-size: 16px;
  line-height: 1.55;
  -webkit-text-size-adjust: 100%;
  overscroll-behavior-y: none;
}

/* `manipulation` desactiva el doble-toque para agrandar y, de paso, mata el
   retardo de 300 ms que el navegador esperaba para ver si venía el segundo
   toque. En una app que se usa con una mano y apurado, ese retardo se siente
   como que el botón no anduvo.
   🔴 En `body` y no sólo en los botones: el doble toque sobre cualquier texto
      también hacía zoom y descolocaba la pantalla. El mapa se excluye abajo
      porque Leaflet necesita gobernar sus propios gestos. */
html, body, button, a, .tarjeta-rol, .tab { touch-action: manipulation; }
.leaflet-container, #mapa { touch-action: none; }
button { font-family: inherit; font-size: inherit; color: inherit; }

.mono, [data-mono] { font-family: var(--pin-font-mono); }

/* ── Cortes de línea ──────────────────────────────────────────────────────
   🔴 Evita la "viuda": una sola palabra colgada sola en el último renglón.
   Pasa con cualquier texto corto y centrado cuya longitud queda apenas por
   encima del ancho disponible, y se lee como un error de maquetado.

   · `balance` reparte los renglones parejos — para títulos, avisos flotantes
     y textos cortos centrados, donde importa la silueta del bloque.
   · `pretty` sólo evita que el último renglón quede huérfano, sin rebalancear
     todo — para párrafos largos, donde `balance` daría renglones cortitos.

   Los navegadores que no lo soportan simplemente cortan como antes: no hay
   nada que se rompa por declararlo. */
.camara-pista,
.camara-espera span,
.resultado h3,
.resultado .motivo,
.menu-marca p,
.tarjeta-rol small,
h2.titulo,
.vacio p,
#va-update .va-update-txt {
  text-wrap: balance;
}

p.bajada,
.aviso div,
.control-txt span,
.instalar-txt span,
.campo .ayuda {
  text-wrap: pretty;
}

/* ── Router de vistas ────────────────────────────────────────────────────────
   Todas las pantallas viven en el mismo documento y se muestran de a una. Es
   una PWA: recargar el HTML en cada paso perdería el stream de la cámara y
   haría parpadear la pantalla en blanco entre paso y paso del registro. */
.vista { display: none; }
.vista.activa { display: flex; flex-direction: column; min-height: 100dvh; }

/* ════════════════════════════════════════════════════════════════════════════
   SPLASH — 3 segundos, fondo blanco, escudo verde
   ════════════════════════════════════════════════════════════════════════════ */
/* `position: relative` para que el pie y la barra de progreso se cuelguen de
   la pantalla del splash y no del bloque contenedor inicial. */
#v-splash {
  position: relative;
  align-items: center; justify-content: center;
  background: #ffffff;
  gap: 0;
}
/* 🔴 UN SOLO ancho para el logo del splash y el del menú. El logo viaja de una
   pantalla a la otra SIN cambiar de tamaño —sólo se desplaza—, así que si los
   dos anchos se separaran habría un salto al terminar el movimiento. Tocar
   este token los mueve a los dos a la vez, que es justamente el punto. */
:root { --logo-ancho: min(76vw, 380px); }

.splash-logo {
  width: var(--logo-ancho);
  color: var(--pin-accent);      /* el SVG usa currentColor */
  opacity: 0;
  transform: scale(.94);
  animation: splash-in 1.1s var(--pin-ease) forwards;
}
.splash-logo svg { width: 100%; height: auto; display: block; }
@keyframes splash-in { to { opacity: 1; transform: scale(1); } }

.splash-pie {
  position: absolute; bottom: calc(34px + env(safe-area-inset-bottom, 0px));
  font-size: 12px; letter-spacing: .16em; text-transform: uppercase;
  color: var(--pin-text-faint); font-family: var(--pin-font-mono);
  opacity: 0; animation: splash-in .8s .5s var(--pin-ease) forwards;
}
/* Barrita de progreso de los 3 s: sin ella la espera se siente colgada. */
.splash-barra {
  position: absolute; bottom: 0; left: 0; height: 3px;
  background: var(--pin-accent); width: 0;
  animation: splash-barra 3s linear forwards;
}
@keyframes splash-barra { to { width: 100%; } }

@media (prefers-reduced-motion: reduce) {
  .splash-logo, .splash-pie { animation-duration: .01ms; opacity: 1; transform: none; }
  .splash-barra { animation: none; width: 100%; }
}

/* ── Transición del logo hacia el menú ────────────────────────────────────
   Ver `arranque.js § transicionAlMenu`. El logo del splash viaja hasta la
   posición que ocupa en el menú, así las dos pantallas se leen como una sola
   escena. Acá va sólo el escenario; el movimiento lo calcula el JS, porque
   depende de medidas reales que cambian con cada tamaño de pantalla. */
body.transicionando { overflow: hidden; }
/* 🔴 `fixed` mientras dura: las vistas son hermanas en flujo normal y con las
   dos visibles se apilarían, duplicando el alto de la página. */
body.transicionando #v-splash { position: fixed; inset: 0; z-index: 60; }

/* Al salir se apagan el rótulo y la barra: lo único que sigue en escena es el
   logo, que es el que viaja. */
#v-splash.saliendo .splash-pie,
#v-splash.saliendo .splash-barra { opacity: 0; transition: opacity .3s ease; }
/* La animación de entrada terminó hace rato; se anula para que no pelee con
   el `transform` que le pone el JS. */
#v-splash.saliendo .splash-logo { animation: none; opacity: 1; }

/* El menú entra escalonado, detrás del logo que todavía está viajando. El
   retraso arranca cuando el logo ya recorrió la mayor parte del camino. */
#v-menu.entrando .menu-marca .logo { opacity: 0; }
#v-menu.entrando .menu-marca h2,
#v-menu.entrando .menu-marca p,
#v-menu.entrando .roles,
#v-menu.entrando #instalar-caja,
#v-menu.entrando .pie-version {
  opacity: 0; transform: translateY(16px);
}
/* 🔴 El logo del menú aparece de golpe, SIN transición. En ese instante el
   logo del splash está exactamente encima —misma posición y mismo tamaño— y
   se retira en el mismo cuadro: el relevo es invisible. Con un desvanecido
   acá, entre que uno se va y el otro llega quedaría un parpadeo sin logo. */
#v-menu .menu-marca .logo { transition: none; }
#v-menu .menu-marca h2,
#v-menu .menu-marca p,
#v-menu .roles,
#v-menu #instalar-caja,
#v-menu .pie-version {
  transition: opacity .42s ease, transform .42s var(--pin-ease);
}
#v-menu .menu-marca h2 { transition-delay: .04s; }
#v-menu .menu-marca p  { transition-delay: .09s; }
#v-menu .roles         { transition-delay: .14s; }
#v-menu #instalar-caja { transition-delay: .20s; }
#v-menu .pie-version { transition-delay: .24s; }

/* ════════════════════════════════════════════════════════════════════════════
   ESTRUCTURA COMÚN
   ════════════════════════════════════════════════════════════════════════════ */
.cabecera {
  display: flex; align-items: center; gap: 12px;
  padding: calc(14px + env(safe-area-inset-top, 0px)) 16px 14px;
  /* ⚰️ Acá había un `border-bottom`. Con el logo centrado y en posición
     absoluta —que es más alto que los botones de la fila— la línea le pasaba
     JUSTO por el medio, entre «Municipalidad de» y «Pinamar». La cabecera es
     opaca y está fija arriba: se distingue del contenido sin necesidad de una
     raya que además cruzaba la marca institucional. */
  background: var(--pin-bg);
  position: sticky; top: 0; z-index: 20;
}
/* 🔴 Un espaciador que le da ALTO a la cabecera. El logo está en posición
   absoluta, así que no cuenta para el alto de la fila: sin esto la cabecera
   mide lo que miden los botones y el logo se le sale por abajo, pisando el
   contenido. Es la causa de que la línea lo cruzara. */
.cabecera::before { content: ''; display: block; width: 0; height: 40px; }
.cabecera h1 { font-size: 17px; font-weight: 600; margin: 0; flex: 1; min-width: 0; }
.cabecera .sub { font-size: 12px; color: var(--pin-text-muted); font-weight: 400; display: block; }

.volver {
  background: none; border: 0; padding: 6px; margin: -6px 0 -6px -6px;
  color: var(--pin-accent); cursor: pointer; display: flex; border-radius: var(--pin-r-sm);
}
.volver:active { background: var(--pin-accent-dim); }

/* ⚠️ 28 px arriba y no 20: con la cabecera sin línea, el contenido pegado
   quedaba como una continuación de ella en vez de como el comienzo de la
   pantalla. El aire es lo que separa ahora, y es lo que hacía la raya. */
.cuerpo { flex: 1; padding: 28px 16px calc(24px + env(safe-area-inset-bottom, 0px)); }
.cuerpo.con-barra { padding-bottom: calc(var(--barra-h) + 20px); }
.centrado { max-width: 520px; margin: 0 auto; width: 100%; }

h2.titulo { font-size: 24px; line-height: 1.2; margin: 0 0 8px; font-weight: 600; letter-spacing: -.01em; }
p.bajada  { color: var(--pin-text-muted); margin: 0 0 24px; font-size: 15px; }

/* ── Marca chica del encabezado ─────────────────────────────────────────── */
.marca { display: flex; align-items: center; gap: 10px; }
.marca-escudo { height: 34px; width: auto; color: var(--pin-accent); flex-shrink: 0; }
.marca-escudo svg { height: 100%; width: auto; display: block; }
.marca-txt b { display: block; font-size: 15px; font-weight: 600; line-height: 1.15; }
.marca-txt span {
  display: block; font-size: 10px; letter-spacing: .14em; text-transform: uppercase;
  color: var(--pin-accent); font-family: var(--pin-font-mono);
}

/* ════════════════════════════════════════════════════════════════════════════
   MENÚ DE SELECCIÓN — las dos tarjetas de rol
   ════════════════════════════════════════════════════════════════════════════ */
/* ── Composición del menú ─────────────────────────────────────────────────
   🔴 Marca ARRIBA, acciones ABAJO. Antes todo iba centrado vertical y el
   resultado era que el bloque flotaba en el medio con aire desaprovechado
   arriba y un vacío grande abajo.

   Anclar resuelve dos cosas de una: el logo gana presencia en el tercio
   superior, y las tarjetas caen en la zona donde llega el pulgar sin
   reacomodar la mano — que es donde tienen que estar los botones en un
   teléfono que se usa parado en la calle. El espacio del medio deja de ser un
   sobrante y pasa a ser respiración deliberada. */
#v-menu .cuerpo { display: flex; flex-direction: column; }
#v-menu .centrado { flex: 1; display: flex; flex-direction: column; }

/* `margin-bottom: auto` empuja todo lo que sigue contra el borde inferior. */
/* ════════════════════════════════════════════════════════════════════════════
   LA CUADRÍCULA — papel milimetrado municipal, sólo en la PUERTA

   🔴 VA EN LAS PANTALLAS DE INGRESO Y NO EN LAS DE TRABAJO, y ésa es toda la
      decisión. Esta pantalla se ve UNA vez por sesión y dura tres segundos: es
      el lugar barato para poner identidad, porque no compite con ningún dato.
      El mapa, la credencial y el escáner se miran cien veces por jornada, y
      ahí una retícula de fondo pelea con los bordes de cada tarjeta hasta
      volverse ruido. Lo mismo que la vuelve buena acá la vuelve mala allá.

   🔴 TRAMA DE PUNTOS, NO RETÍCULA. Se probaron las dos. Las líneas dibujan
      ESTRUCTURA: cruzan la pantalla entera y arman una cuadrícula que compite
      con el borde de cada tarjeta, sobre todo a pantalla completa. Los puntos
      dan TEXTURA y no proponen ninguna geometría, así que se pueden mirar sin
      verlos — que es lo único que se le pide a un fondo.

   ⚠️ Y por eso los puntos llevan MÁS color que las líneas: una línea cruza toda
      la pantalla y suma mucha tinta, un punto ocupa dos píxeles. Al 10 % —el
      valor que servía para la retícula— la trama directamente desaparecía.

   🔴 EL PASO CRECE CON EL ANCHO. Un número fijo da quince marcas en un teléfono
      y setenta y cinco en un monitor: el mismo valor produce dos fondos
      distintos. Lo que hay que mantener parejo es cuántas marcas se ven, no
      cuánto mide el paso.

   ⚠️ Va como `background` de la vista y NO como un `::before`: un
      pseudo-elemento posicionado necesitaría z-index, y ahí es donde estas
      cosas terminan tapando el contenido. Con `background` el orden de pintado
      lo resuelve el navegador y no hay nada que ajustar.

   ⚠️ `v-camara` queda AFUERA a propósito — es negra, con la previsualización
      de la cámara a pantalla completa. Una cuadrícula ahí sería un defecto.
   ════════════════════════════════════════════════════════════════════════════ */
#v-menu,
#v-ingreso,
#v-registro-dni,
#v-registro-datos,
#v-registro-sms,
#v-verificacion-intro,
#v-verificacion-revisar {
  background-color: #fcfefd;
  background-image: radial-gradient(var(--pin-grid-fina) 1.2px, transparent 1.3px);
  background-size: var(--pin-grid-paso) var(--pin-grid-paso);
}

/* El paso sube con el ancho para que la cantidad de marcas a la vista se
   mantenga parecida: en un teléfono la app se ve como la maqueta, y en un
   monitor no se convierte en papel de ingeniería. */
@media (min-width: 640px)  { :root { --pin-grid-paso: 28px; } }
@media (min-width: 1100px) { :root { --pin-grid-paso: 34px; } }

.menu-marca {
  text-align: center;
  padding-top: clamp(10px, 5vh, 54px);
  margin-bottom: auto;
}
/* Aire mínimo garantizado entre la marca y las tarjetas en pantallas chicas,
   donde el `auto` se queda sin espacio que repartir. */
.menu-marca + .roles { margin-top: 34px; }
.menu-marca .logo { width: var(--logo-ancho); color: var(--pin-accent); margin: 0 auto 22px; }
.menu-marca .logo svg { width: 100%; height: auto; display: block; }
.menu-marca h2 { font-size: 21px; margin: 0 0 5px; font-weight: 600; letter-spacing: -.01em; }
/* La bajada en versalitas espaciadas: se lee como rótulo institucional y no
   compite con el título, que es lo que tiene que mandar en la jerarquía. */
.menu-marca p {
  margin: 0; color: var(--pin-text-muted);
  font-size: 11.5px; font-weight: 500;
  letter-spacing: .13em; text-transform: uppercase;
}

/* Pie: sólo la versión, discreta. Se aparta del bloque de tarjetas para que
   no parezca parte de la acción. */
.pie-version {
  text-align: center; margin: 20px 0 0;
  font-family: var(--pin-font-mono); font-size: 10.5px;
  letter-spacing: .1em; color: var(--pin-text-faint); opacity: .6;
}

.roles { display: flex; flex-direction: column; gap: 14px; }

.tarjeta-rol {
  display: flex; align-items: center; gap: 16px; width: 100%;
  padding: 22px 20px; text-align: left; cursor: pointer;
  background: var(--pin-surface); color: var(--pin-text);
  border: 1px solid var(--pin-line); border-radius: var(--pin-r-lg);
  box-shadow: var(--pin-shadow-card);
  transition: transform var(--pin-t) var(--pin-ease), border-color var(--pin-t), background var(--pin-t);
}
.tarjeta-rol:active { transform: scale(.985); background: var(--pin-bg-suave); }
@media (hover: hover) {
  .tarjeta-rol:hover { border-color: var(--pin-line-strong); transform: translateY(-2px); }
}
.tarjeta-rol .ic {
  width: 54px; height: 54px; flex-shrink: 0; border-radius: var(--pin-r-md);
  background: var(--pin-accent-dim); color: var(--pin-accent);
  display: flex; align-items: center; justify-content: center;
}
.tarjeta-rol .ic svg { width: 28px; height: 28px; }
.tarjeta-rol b { display: block; font-size: 18px; font-weight: 600; line-height: 1.2; }
.tarjeta-rol small { display: block; color: var(--pin-text-muted); font-size: 13.5px; margin-top: 3px; line-height: 1.4; }
.tarjeta-rol .chev { margin-left: auto; color: var(--pin-text-faint); flex-shrink: 0; }

/* La tarjeta del inspector se lee como "área restringida": relleno verde en vez
   de blanco. Así nadie entra ahí por curiosidad creyendo que es para todos. */
.tarjeta-rol.inspector { background: var(--pin-accent); border-color: var(--pin-accent); color: #fff; }
.tarjeta-rol.inspector .ic { background: rgba(255, 255, 255, .16); color: #fff; }
.tarjeta-rol.inspector small { color: rgba(255, 255, 255, .78); }
.tarjeta-rol.inspector .chev { color: rgba(255, 255, 255, .6); }
.tarjeta-rol.inspector:active { background: var(--pin-accent-soft); }

/* ── Invitación a instalar ───────────────────────────────────────────────── */
.instalar {
  display: flex; align-items: center; gap: 13px;
  margin-top: 22px; padding: 14px 15px;
  background: var(--pin-bg-suave); border: 1px solid var(--pin-line);
  border-radius: var(--pin-r-md); position: relative;
}
.instalar.vertical { align-items: flex-start; }
.instalar-ic {
  width: 38px; height: 38px; flex-shrink: 0; border-radius: var(--pin-r-sm);
  background: var(--pin-accent-dim); color: var(--pin-accent);
  display: flex; align-items: center; justify-content: center;
}
.instalar-ic svg { width: 20px; height: 20px; }
.instalar-txt { flex: 1; min-width: 0; }
.instalar-txt b { display: block; font-size: 14.5px; font-weight: 600; line-height: 1.25; }
.instalar-txt span { display: block; font-size: 13px; color: var(--pin-text-muted); line-height: 1.45; margin-top: 2px; }
.instalar-txt span b { display: inline; font-size: inherit; color: var(--pin-text); }
.instalar .btn.chico { flex-shrink: 0; }
/* Android — el botón lleva al APK y va en su propio renglón, a lo ancho. El
   texto tiene que explicar por qué hace falta la app (tres renglones) y un
   botón al costado lo dejaría en una columna de dos palabras. */
.instalar.apk { flex-wrap: wrap; }
.instalar.apk .instalar-txt b { padding-right: 28px; }   /* deja lugar al ✕ */
.instalar.apk .btn.chico { width: 100%; margin-top: 12px; }
a.btn { text-decoration: none; }
/* El ícono de Compartir de iOS, en medio de la frase: sin verlo, "tocá
   compartir" no le dice nada a alguien que nunca lo usó. */
.ic-en-linea { width: 15px; height: 15px; vertical-align: -3px; margin: 0 1px; }
.cerrar-instalar {
  position: absolute; top: 6px; right: 6px;
  width: 30px; height: 30px; padding: 0; cursor: pointer;
  background: none; border: 0; border-radius: var(--pin-r-sm);
  color: var(--pin-text-faint); font-size: 15px; line-height: 1;
}
.cerrar-instalar:active { background: rgba(0, 0, 0, .06); }

/* ════════════════════════════════════════════════════════════════════════════
   FORMULARIOS
   ════════════════════════════════════════════════════════════════════════════ */
.campo { margin-bottom: 18px; }
.campo > label {
  display: block; font-size: 11.5px; font-weight: 600; letter-spacing: .07em;
  text-transform: uppercase; color: var(--pin-text-muted); margin-bottom: 7px;
}
.campo input, .campo select, .campo textarea {
  width: 100%; padding: 14px 15px;
  /* 16px exactos: por debajo de eso, Safari en iOS hace zoom al enfocar el
     campo y descoloca toda la pantalla. */
  font-size: 16px; font-family: inherit; color: var(--pin-text);
  background: var(--pin-surface);
  border: 1.5px solid var(--pin-line); border-radius: var(--pin-r-md);
  transition: border-color var(--pin-t);
}
.campo input:focus, .campo select:focus, .campo textarea:focus {
  outline: none; border-color: var(--pin-accent);
}
.campo input[inputmode="numeric"] { font-family: var(--pin-font-mono); letter-spacing: .06em; }
/* ── Mostrar/ocultar la clave ────────────────────────────────────────────
   El envoltorio lo crea `auth.js` en tiempo de ejecución sobre cada
   input[type=password], así cualquier campo de clave que se agregue después
   lo hereda solo. El botón mide 44px: es el mínimo para tocar con el pulgar
   sin errarle, y va DENTRO del campo para no correr el layout. */
.con-ojo { position: relative; display: block; }
.con-ojo input { padding-right: 52px; }
.ojo {
  position: absolute; right: 5px; top: 50%; transform: translateY(-50%);
  width: 44px; height: 44px; padding: 0; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  background: none; border: 0; border-radius: var(--pin-r-sm);
  color: var(--pin-text-faint);
}
.ojo:hover, .ojo:focus-visible { color: var(--pin-accent); outline: none; }
.ojo:active { background: var(--pin-accent-dim); }
.ojo svg { width: 21px; height: 21px; }

.campo .ayuda { font-size: 12.5px; color: var(--pin-text-faint); margin-top: 6px; }
.campo.error input { border-color: var(--pin-danger); }
.campo .msg-error { font-size: 13px; color: var(--pin-danger); margin-top: 6px; display: none; }
.campo.error .msg-error { display: block; }

.btn {
  display: flex; align-items: center; justify-content: center; gap: 9px;
  width: 100%; padding: 16px 20px; cursor: pointer;
  font-size: 16px; font-weight: 600;
  background: var(--pin-accent); color: var(--pin-accent-ink);
  border: 1.5px solid var(--pin-accent); border-radius: var(--pin-r-md);
  transition: transform .12s var(--pin-ease), opacity var(--pin-t);
}
.btn:active { transform: scale(.985); }
.btn:disabled { opacity: .45; cursor: default; transform: none; }
.btn.secundario { background: transparent; color: var(--pin-accent); }
.btn.fantasma { background: transparent; color: var(--pin-text-muted); border-color: transparent; font-weight: 500; }
.btn.peligro { background: var(--pin-danger); border-color: var(--pin-danger); color: #fff; }
/* 🔴 EL ROJO LLENO SE RESERVA PARA LA PANTALLA QUE EXISTE PARA ESO. Un bloque
   carmesí a todo el ancho es el elemento más pesado de la pantalla, y eso sólo
   corresponde cuando borrar ES el propósito — o sea, en la de confirmación. En
   los ajustes de la cuenta, donde borrar es una opción entre otras, el mismo
   botón grita y desbalancea todo lo demás.
   ⚠️ La advertencia no se pierde: el rojo sigue en el borde y en el texto. Lo
      que se saca es el peso, no la señal. */
.btn.peligro.contorno {
  background: transparent; color: var(--pin-danger); border-color: var(--pin-danger);
}
.btn.peligro.contorno:active { background: rgba(190, 18, 60, .06); }
.btn.chico { padding: 10px 14px; font-size: 14px; width: auto; }
.btn + .btn { margin-top: 10px; }
.btn svg { width: 19px; height: 19px; flex-shrink: 0; }

.fila-btn { display: flex; gap: 10px; }
.fila-btn .btn { margin-top: 0; }
/* Los dos caminos de la puerta de la jornada, adentro del propio aviso. */
.aviso .fila-btn { margin-top: 12px; }

/* ── Avisos ─────────────────────────────────────────────────────────────── */
.aviso {
  display: flex; gap: 11px; padding: 14px 16px; margin-bottom: 18px;
  border-radius: var(--pin-r-md); font-size: 14px; line-height: 1.5;
  border: 1px solid; align-items: flex-start;
}
.aviso svg { width: 19px; height: 19px; flex-shrink: 0; margin-top: 2px; }
.aviso.info    { background: rgba(3, 105, 161, .07);  border-color: rgba(3, 105, 161, .28);  color: #075985; }
.aviso.ok      { background: var(--pin-accent-dim);   border-color: var(--pin-line-strong);  color: #04503a; }
.aviso.atencion{ background: rgba(180, 83, 9, .07);   border-color: rgba(180, 83, 9, .28);   color: #92400e; }
.aviso.error   { background: rgba(190, 18, 60, .06);  border-color: rgba(190, 18, 60, .26);  color: #9f1239; }
.aviso b { display: block; margin-bottom: 2px; }

/* ── Pasos del registro ─────────────────────────────────────────────────── */
.pasos { display: flex; gap: 6px; padding: 0 16px 14px; }
.pasos i {
  flex: 1; height: 4px; border-radius: 99px; background: var(--pin-line);
  transition: background var(--pin-t);
}
.pasos i.hecho, .pasos i.actual { background: var(--pin-accent); }
.pasos i.actual { animation: pulso 1.6s ease-in-out infinite; }
@keyframes pulso { 50% { opacity: .45; } }

/* ════════════════════════════════════════════════════════════════════════════
   CÁMARA — captura de DNI y de rostro
   ════════════════════════════════════════════════════════════════════════════ */
.camara {
  position: relative; flex: 1; min-height: 0;
  background: #04150c; overflow: hidden;
  display: flex; align-items: center; justify-content: center;
}
.camara video {
  width: 100%; height: 100%; object-fit: cover; display: block;
  background: #04150c;
  /* 🔴 Invisible hasta que llegan cuadros de verdad (`.listo` lo enciende).
     Un <video> sin stream hace que el WebView de Android pinte su control de
     reproducción por defecto: un botón de play enorme sobre fondo gris, justo
     mientras el sistema pregunta por el permiso. Se lee como una pantalla rota
     y es lo primero que ve alguien al empezar la verificación. */
  opacity: 0;
  transition: opacity .4s var(--pin-ease);
}
.camara.listo video { opacity: 1; }
/* Y por las dudas, se desactivan los controles nativos en los motores que los
   exponen igual aunque el elemento no los pida. */
.camara video::-webkit-media-controls,
.camara video::-webkit-media-controls-start-playback-button {
  display: none !important; -webkit-appearance: none;
}
/* La cámara frontal se ve espejada: es lo que la gente espera de un selfie, y
   sin el espejo "girá a la izquierda" manda para el lado contrario. */
.camara.frontal video { transform: scaleX(-1); }

/* ── Estado de espera ──────────────────────────────────────────────────────
   Lo que se ve mientras el sistema pide el permiso y la cámara arranca. Con
   la marca municipal y un pulso lento: comunica "estamos trabajando", no
   "esto se colgó". Desaparece en cuanto entra la primera imagen. */
.camara-espera {
  position: absolute; inset: 0; z-index: 3;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 6px; padding: 32px; text-align: center;
  background: #04150c; color: #fff;
  transition: opacity .4s var(--pin-ease), visibility .4s;
}
.camara.listo .camara-espera { opacity: 0; visibility: hidden; }
.camara-espera-escudo { height: 62px; margin-bottom: 14px; opacity: .9; }
.camara-espera-escudo img { height: 100%; width: auto; display: block; }
.camara-espera b { font-size: 17px; font-weight: 600; }
.camara-espera span { font-size: 14px; color: rgba(255, 255, 255, .68); line-height: 1.45; max-width: 260px; }
.camara-espera-pulso {
  width: 42px; height: 3px; border-radius: 99px; margin-top: 20px;
  background: var(--pin-accent-soft);
  animation: espera-pulso 1.5s ease-in-out infinite;
}
@keyframes espera-pulso {
  0%, 100% { opacity: .25; transform: scaleX(.55); }
  50%      { opacity: 1;   transform: scaleX(1); }
}

.camara-overlay {
  position: absolute; inset: 0; pointer-events: none;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
}
/* Marco del DNI: proporción ISO ID-1 (85.6 × 54 mm) — la real, porque es lo
   que hace que el documento entre completo y la foto salga legible.

   Sin borde continuo: cuatro esquineros. Un rectángulo cerrado cruza el
   documento con una línea justo donde está el número, y además se lee como
   "recorte". Los esquineros dicen "encuadrá acá" sin taparle nada al usuario,
   y son el lenguaje que ya conocen de las apps de banco. */
.marco-dni {
  position: relative;
  width: min(86vw, 420px); aspect-ratio: 85.6 / 54;
  border-radius: 16px;
  box-shadow: 0 0 0 100vmax rgba(0, 0, 0, .58);
  transition: transform var(--pin-t) var(--pin-ease);
}
.marco-dni > i {
  position: absolute; width: 30px; height: 30px;
  border: 3px solid #fff;
  transition: border-color var(--pin-t), width var(--pin-t) var(--pin-ease), height var(--pin-t) var(--pin-ease);
}
.marco-dni > i:nth-child(1) { top: -3px;    left: -3px;  border-right: 0; border-bottom: 0; border-radius: 16px 0 0 0; }
.marco-dni > i:nth-child(2) { top: -3px;    right: -3px; border-left: 0;  border-bottom: 0; border-radius: 0 16px 0 0; }
.marco-dni > i:nth-child(3) { bottom: -3px; left: -3px;  border-right: 0; border-top: 0;    border-radius: 0 0 0 16px; }
.marco-dni > i:nth-child(4) { bottom: -3px; right: -3px; border-left: 0;  border-top: 0;    border-radius: 0 0 16px 0; }

/* Verde y esquineros más largos cuando la imagen ya sirve: el marco "abraza"
   el documento un instante antes del disparo. */
.marco-dni.ok > i  { border-color: #4ade80; width: 44px; height: 44px; }
.marco-dni.ok      { transform: scale(1.012); }
.marco-dni.mal > i { border-color: rgba(255, 255, 255, .82); }

.marco-rostro {
  width: min(68vw, 300px); aspect-ratio: 1 / 1.25;
  border: 3px solid rgba(255, 255, 255, .9);
  border-radius: 50%;
  box-shadow: 0 0 0 100vmax rgba(0, 0, 0, .58);
  transition: border-color var(--pin-t), transform var(--pin-t) var(--pin-ease);
}
.marco-rostro > i { display: none; }         /* los esquineros son sólo del DNI */
.marco-rostro.ok  { border-color: rgba(74, 222, 128, .35); border-width: 4px; transform: scale(1.02); }
.marco-rostro.mal { border-color: rgba(255, 255, 255, .55); }

/* ── Anillo de progreso del rostro ────────────────────────────────────────
   Se completa mientras la persona SOSTIENE la posición. Es mucho más claro
   que una barra abajo: el avance ocurre alrededor de su propia cara, que es
   donde tiene la vista puesta. */
.anillo {
  position: absolute; pointer-events: none;
  width: min(68vw, 300px); aspect-ratio: 1 / 1.25;
  opacity: 0; transition: opacity var(--pin-t);
  overflow: visible;
}
.anillo ellipse {
  fill: none; stroke: #4ade80; stroke-width: 3.2;
  stroke-linecap: round;
  stroke-dasharray: 100;
  stroke-dashoffset: 100;                    /* 100 = vacío · 0 = completo */
  /* Arranca arriba y avanza en el sentido de las agujas del reloj. Sin esto
     empezaría a las 3 en punto, que se lee como si hubiera arrancado a mitad. */
  transform: rotate(-90deg); transform-origin: 50% 50%;
  transition: stroke-dashoffset .12s linear;
  filter: drop-shadow(0 0 6px rgba(74, 222, 128, .55));
}
/* Sólo existe en el paso del rostro; en el del DNI el marco son esquineros. */
.camara.rostro .anillo { opacity: 1; }

/* ── Silueta con flecha de giro ───────────────────────────────────────────
   Le dice hacia dónde girar sin depender de que lea el texto — que importa
   con un público que usa esto apurado, al sol y con una mano. */
.guia {
  position: absolute; pointer-events: none;
  /* Justo debajo del óvalo: la mitad de su alto, más aire. */
  top: calc(50% + min(42.5vw, 187px) + 18px);
  width: 78px; color: #fff;
  opacity: 0; transform: translateY(6px);
  transition: opacity var(--pin-t), transform var(--pin-t) var(--pin-ease);
  filter: drop-shadow(0 2px 10px rgba(0, 0, 0, .6));
}
.guia svg { width: 100%; height: auto; display: block; }
.camara.girar .guia { opacity: 1; transform: translateY(0); }
/* El dibujo mira a la izquierda por defecto; para el otro lado se espeja. */
.camara.girar-derecha .guia { transform: translateY(0) scaleX(-1); }

/* La flecha se dibuja sola, una y otra vez: transmite el movimiento que hay
   que hacer, no una dirección estática. */
.guia-flecha { stroke-dasharray: 66; animation: guia-trazo 1.9s ease-in-out infinite; }
.guia-punta  { animation: guia-punta 1.9s ease-in-out infinite; }
@keyframes guia-trazo {
  0%        { stroke-dashoffset: 66; }
  55%, 100% { stroke-dashoffset: 0; }
}
@keyframes guia-punta {
  0%, 40%   { opacity: 0; }
  60%, 100% { opacity: 1; }
}
/* Al lograr la pose, la guía se apaga: ya no hay nada que indicar y sacarla
   confirma que el sistema entendió. */
.camara.girar.logrado .guia { opacity: .25; }

@media (prefers-reduced-motion: reduce) {
  .guia-flecha, .guia-punta { animation: none; stroke-dashoffset: 0; opacity: 1; }
}

.camara-instruccion {
  position: absolute; top: calc(18px + env(safe-area-inset-top, 0px)); left: 16px; right: 16px;
  text-align: center; color: #fff; font-size: 16px; font-weight: 600;
  text-shadow: 0 2px 12px rgba(0, 0, 0, .7); line-height: 1.35;
}
.camara-instruccion small {
  display: block; font-weight: 400; font-size: 13.5px;
  color: rgba(255, 255, 255, .82); margin-top: 4px;
}
.camara-pie {
  position: absolute; left: 0; right: 0;
  bottom: calc(22px + env(safe-area-inset-bottom, 0px));
  display: flex; align-items: center; justify-content: center; gap: 22px;
  padding: 0 20px;
}
.obturador {
  width: 74px; height: 74px; border-radius: 50%; cursor: pointer;
  background: #fff; border: 5px solid rgba(255, 255, 255, .45);
  box-shadow: 0 6px 24px rgba(0, 0, 0, .4);
  transition: transform .12s var(--pin-ease);
}
.obturador:active { transform: scale(.9); }
.obturador:disabled { opacity: .4; }

/* ── Guía en vivo del disparo automático ──────────────────────────────────
   La pista va ARRIBA de la barra y las dos justo encima del borde inferior:
   es donde cae la mirada cuando alguien sostiene el documento con la otra
   mano, sin tapar el recuadro. */
.camara-pista {
  position: absolute; left: 16px; right: 16px;
  /* Por encima del obturador, que aparece como válvula de escape a los 9 s.
     Las posiciones son fijas para que al aparecer el botón no salte nada. */
  bottom: calc(122px + env(safe-area-inset-bottom, 0px));
  text-align: center; pointer-events: none;
  font-size: 15px; font-weight: 600; line-height: 1.35; color: #fff;
  background: rgba(4, 21, 12, .78); border-radius: var(--pin-r-pill);
  padding: 10px 16px; margin: 0 auto; max-width: 420px;
  opacity: 0; transform: translateY(8px);
  transition: opacity var(--pin-t), transform var(--pin-t) var(--pin-ease), background var(--pin-t);
}
.camara-pista.visible { opacity: 1; transform: translateY(0); }
.camara-pista.bien { background: rgba(0, 114, 66, .92); }

.camara-barra {
  position: absolute; left: 16px; right: 16px;
  bottom: calc(106px + env(safe-area-inset-bottom, 0px));
  height: 5px; border-radius: 99px; background: rgba(255, 255, 255, .18);
  overflow: hidden; margin: 0 auto; max-width: 420px; pointer-events: none;
}
.camara-barra i {
  display: block; height: 100%; width: 0;
  background: #4ade80; border-radius: 99px;
  transition: width .1s linear;
}

/* Destello blanco del disparo. Sin esto, una foto que se saca sola pasa
   desapercibida y la persona sigue posando sin saber que ya está. */
.camara-destello {
  position: absolute; inset: 0; background: #fff; opacity: 0; pointer-events: none;
}
.camara-destello.activo { animation: destellar .32s ease-out; }
@keyframes destellar { 0% { opacity: .85; } 100% { opacity: 0; } }
.grabando {
  position: absolute; top: calc(18px + env(safe-area-inset-top, 0px)); right: 18px;
  display: flex; align-items: center; gap: 7px;
  background: rgba(190, 18, 60, .92); color: #fff;
  padding: 6px 12px; border-radius: 99px; font-size: 12px; font-weight: 600;
  font-family: var(--pin-font-mono);
}
.grabando::before {
  content: ''; width: 8px; height: 8px; border-radius: 50%; background: #fff;
  animation: pulso 1.2s ease-in-out infinite;
}

/* ── Resumen de lo que verificó el sistema ────────────────────────────────
   Reemplaza a las miniaturas en la pantalla previa al envío. Dice qué se
   comprobó, no "mirá si está bien". */
.control-lista { display: flex; flex-direction: column; gap: 10px; margin-bottom: 22px; }
.control {
  display: flex; align-items: flex-start; gap: 12px; padding: 13px 14px;
  border: 1px solid var(--pin-line); border-radius: var(--pin-r-md);
  background: var(--pin-surface);
}
.control-ic {
  width: 26px; height: 26px; flex-shrink: 0; border-radius: 50%;
  display: flex; align-items: center; justify-content: center; color: #fff;
}
.control-ic svg { width: 15px; height: 15px; }
.control.ok    { border-color: var(--pin-line-strong); background: var(--pin-accent-dim); }
.control.ok    .control-ic { background: var(--pin-accent); }
.control.aviso { border-color: rgba(180, 83, 9, .3); background: rgba(180, 83, 9, .05); }
.control.aviso .control-ic { background: var(--pin-warn); }
.control.falta { border-color: rgba(190, 18, 60, .28); background: rgba(190, 18, 60, .04); }
.control.falta .control-ic { background: var(--pin-danger); }
.control-txt { flex: 1; min-width: 0; }
.control-txt b { display: block; font-size: 14.5px; font-weight: 600; line-height: 1.3; }
.control-txt span {
  display: block; font-size: 13px; color: var(--pin-text-muted);
  line-height: 1.45; margin-top: 2px;
}
/* El dato leído del documento va en mono: es un número que se compara. */
.control.ok .control-txt span { color: rgba(4, 80, 58, .85); font-family: var(--pin-font-mono); font-size: 12.5px; }

/* Miniaturas de lo capturado */
.capturas { display: flex; gap: 10px; margin-bottom: 20px; flex-wrap: wrap; }
.captura-min {
  width: 88px; border-radius: var(--pin-r-sm); overflow: hidden;
  border: 1.5px solid var(--pin-line); background: var(--pin-bg-suave);
}
.captura-min img { width: 100%; display: block; aspect-ratio: 4/3; object-fit: cover; }
.captura-min span {
  display: block; font-size: 10px; text-align: center; padding: 4px 2px;
  color: var(--pin-text-muted); font-family: var(--pin-font-mono);
}

/* ════════════════════════════════════════════════════════════════════════════
   CREDENCIAL DEL VENDEDOR
   ════════════════════════════════════════════════════════════════════════════ */
.credencial {
  background: var(--pin-surface); border: 1px solid var(--pin-line);
  border-radius: var(--pin-r-lg); box-shadow: var(--pin-shadow-card);
  overflow: hidden; margin-bottom: 18px;
}
.credencial-top {
  background: var(--pin-accent); color: #fff; padding: 16px 18px;
  display: flex; align-items: center; gap: 12px;
}
.credencial-top .escudo { height: 36px; color: #fff; flex-shrink: 0; }
.credencial-top .escudo svg { height: 100%; width: auto; display: block; }
.credencial-top b { display: block; font-size: 14px; font-weight: 600; line-height: 1.2; }
.credencial-top span {
  display: block; font-size: 10px; letter-spacing: .14em; text-transform: uppercase;
  opacity: .85; font-family: var(--pin-font-mono);
}

.qr-caja {
  padding: 22px 18px 14px; display: flex; flex-direction: column; align-items: center;
  background: #fff;
}
.qr-caja canvas, .qr-caja img {
  width: min(62vw, 250px); height: auto; image-rendering: pixelated; display: block;
}
.qr-vencimiento {
  margin-top: 12px; font-size: 12.5px; color: var(--pin-text-muted);
  font-family: var(--pin-font-mono); display: flex; align-items: center; gap: 7px;
}
.qr-vencimiento i {
  width: 7px; height: 7px; border-radius: 50%; background: var(--pin-ok);
  animation: pulso 2s ease-in-out infinite;
}

.credencial-datos { padding: 4px 18px 18px; }
.dato { display: flex; justify-content: space-between; gap: 14px; padding: 9px 0; border-bottom: 1px solid var(--pin-line); }
.dato:last-child { border-bottom: 0; }
.dato dt { font-size: 12px; color: var(--pin-text-muted); margin: 0; flex-shrink: 0; }
.dato dd { margin: 0; font-size: 14px; font-weight: 600; text-align: right; font-family: var(--pin-font-mono); }

.chip {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 5px 11px; border-radius: 99px;
  font-size: 11.5px; font-weight: 600; letter-spacing: .05em; text-transform: uppercase;
  font-family: var(--pin-font-mono);
}
.chip.ok       { background: var(--pin-accent-dim); color: #04503a; }
.chip.espera   { background: rgba(180, 83, 9, .1);  color: #92400e; }
.chip.error    { background: rgba(190, 18, 60, .09); color: #9f1239; }
.chip.neutro   { background: var(--pin-bg-suave);   color: var(--pin-text-muted); }

/* ── Continuar con Google ──────────────────────────────────────────────────
   Blanco con borde y el logo en sus cuatro colores, como pide la marca de
   Google. No va en verde institucional: la gente lo reconoce por la forma, y un
   botón de Google teñido de verde se lee como una imitación. */
.btn.google {
  background: #fff; color: #1f1f1f; border-color: var(--pin-line-strong); font-weight: 500;
}
.btn.google:active { background: var(--pin-bg-suave); }
.btn.google svg { width: 19px; height: 19px; }
/* 🔴 `oculto` NO EXISTÍA EN ESTA HOJA. Existe en `instalar.html` y en
   `relevamiento-admin.html` —archivos aparte, cada uno con su CSS embebido— y
   se dio por sentada acá. Resultado: la clase no ocultaba nada. La pantalla de
   ingreso mostraba sus DOS pasos a la vez, y el botón de Google quedaba visible
   dentro de cáscaras que no pueden usarlo; al tocarlo caía en el popup, que en
   un WebView se cuelga sin resolver nunca.

   ⚠️ Se excluye `#va-update`, que ya tenía su propia regla de `oculto` con
      opacidad y desplazamiento: un `display:none` global le rompería la
      transición del cartel de actualización. */
.oculto:not(#va-update) { display: none !important; }

/* El saludo del segundo paso. Discreto: confirma, no celebra. */
.saludo {
  display: flex; align-items: center; gap: 10px;
  padding: 12px 14px; margin-bottom: 18px;
  background: var(--pin-accent-dim); border-radius: var(--pin-r-md);
}
.saludo svg { width: 20px; height: 20px; color: var(--pin-accent); flex-shrink: 0; }
.saludo b { display: block; font-size: 14.5px; font-weight: 600; }
.saludo span { display: block; font-size: 12.5px; color: var(--pin-text-muted); }

/* Deliberadamente sin cuerpo de botón: es la salida para un caso minoritario y
   no puede competir con la puerta principal. */
.enlace-suave {
  background: none; border: 0; padding: 4px; cursor: pointer; font: inherit;
  font-size: 13.5px; color: var(--pin-text-muted);
  text-decoration: underline; text-underline-offset: 3px;
}
.enlace-suave:active { color: var(--pin-accent); }

.separador-o { display: flex; align-items: center; gap: 12px; margin: 18px 0 14px; }
.separador-o span { flex: 1; height: 1px; background: var(--pin-line); }
.separador-o small { font-size: 12px; color: var(--pin-text-faint); }










/* Scroll propio: la lista de teléfonos es larga y el cajón no puede empujar el
   scroll de la página que quedó abajo. */
.menu-cuerpo {
  flex: 1; overflow-y: auto; -webkit-overflow-scrolling: touch;
  padding: 4px 16px max(20px, env(safe-area-inset-bottom));
}
.menu-bloque { padding: 16px 0; border-bottom: 1px solid var(--pin-line); }
.menu-bloque:last-of-type { border-bottom: 0; }
.menu-bloque > h3 {
  font-size: 11.5px; font-weight: 600; letter-spacing: .08em; text-transform: uppercase;
  color: var(--pin-text-muted); margin: 0 0 10px;
}
.menu-sub {
  font-size: 11.5px; font-weight: 600; letter-spacing: .06em; text-transform: uppercase;
  color: var(--pin-text-faint); margin: 14px 0 6px;
}
.menu-vacio { font-size: 13.5px; color: var(--pin-text-muted); margin: 0; }

.menu-item {
  display: flex; align-items: center; gap: 10px; width: 100%; cursor: pointer;
  padding: 11px 12px; margin-bottom: 6px; border-radius: var(--pin-r-sm);
  background: var(--pin-bg-suave); border: 1px solid var(--pin-line);
  font: inherit; text-align: left; color: inherit;
}
.menu-item:active { background: var(--pin-accent-dim); }
.menu-item span { flex: 1; min-width: 0; font-size: 13px; color: var(--pin-text-muted); line-height: 1.4; }
.menu-item span b { display: block; font-size: 14.5px; font-weight: 600; color: var(--pin-text); }
.menu-item svg { width: 18px; height: 18px; flex-shrink: 0; color: var(--pin-text-faint); }
.menu-tilde { flex: 0 0 auto; color: var(--pin-accent); font-size: 17px; font-weight: 700; }

/* ── El clima ──────────────────────────────────────────────────────────────
   Dos por fila: entran en el ancho del cajón sin achicar la tipografía, y el
   par que importa —viento y temperatura— queda en el primer renglón. */
.clima-fila { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-bottom: 8px; }
.clima-dato {
  background: var(--pin-bg-suave); border: 1px solid var(--pin-line);
  border-radius: var(--pin-r-sm); padding: 10px 12px;
}
/* Ámbar, no rojo: viento fuerte o UV alto no es una emergencia, es algo a
   tener en cuenta. El rojo de esta app está reservado para lo que invalida. */
.clima-dato.alerta { background: rgba(180, 83, 9, .07); border-color: rgba(180, 83, 9, .28); }
.clima-dato .rot { display: block; font-size: 11px; letter-spacing: .05em; text-transform: uppercase;
  color: var(--pin-text-muted); }
.clima-dato b { display: block; font-size: 18px; font-weight: 600; line-height: 1.2; margin-top: 3px;
  font-family: var(--pin-font-mono); }
.clima-dato.alerta b { color: #92400e; }
.clima-dato .det { display: block; font-size: 11.5px; color: var(--pin-text-muted); margin-top: 2px; }
.clima-pie { font-size: 11px; color: var(--pin-text-faint); margin: 6px 0 0; text-align: right; }

/* ── Si pasa algo ──────────────────────────────────────────────────────────
   🔴 Ojos en Alerta con cuerpo de botón y arriba de todo: es lo único de esta
      pantalla que se busca con apuro. Los teléfonos van como lista porque se
      leen; esto se toca.
   ⚠️ No va en verde WhatsApp — mismo criterio que el relevamiento: un bloque
      #25D366 en una pantalla institucional se lee como una calcomanía. */
.menu-ojos {
  display: flex; align-items: flex-start; gap: 11px; text-decoration: none;
  padding: 13px 14px; border-radius: var(--pin-r-md); margin-bottom: 12px;
  background: var(--pin-accent); color: var(--pin-accent-ink);
}
.menu-ojos svg { width: 22px; height: 22px; flex-shrink: 0; margin-top: 1px; }
.menu-ojos span { font-size: 12.5px; line-height: 1.45; color: rgba(255, 255, 255, .85); }
.menu-ojos span b { display: block; font-size: 15px; font-weight: 600; color: #fff; margin-bottom: 2px; }

.menu-tel {
  display: flex; align-items: center; gap: 10px; text-decoration: none; color: inherit;
  padding: 10px 4px; border-bottom: 1px solid var(--pin-line); font-size: 14px;
}
.menu-tel:last-child { border-bottom: 0; }
.menu-tel span { flex: 1; min-width: 0; }
.menu-tel b { font-family: var(--pin-font-mono); font-size: 14px; color: var(--pin-accent); white-space: nowrap; }
.menu-tel.urgente b { font-size: 17px; }
.menu-tel.destacado { background: var(--pin-accent-dim); border-radius: var(--pin-r-sm);
  padding-left: 10px; padding-right: 10px; border-bottom-color: transparent; }

.menu-salir {
  width: 100%; margin: 18px 0 8px; padding: 12px; cursor: pointer;
  background: none; border: 1px solid var(--pin-line); border-radius: var(--pin-r-sm);
  font: inherit; font-size: 14px; color: var(--pin-text-muted);
}
.menu-salir:active { background: var(--pin-bg-suave); }

/* ── Filtros del mapa por tramo de costa ───────────────────────────────────
   Píldoras, no un desplegable: son seis opciones y el inspector las usa parado
   en la calle, con una mano. Un `select` obliga a abrir, buscar y confirmar.
   ⚠️ Scroll horizontal propio: en un teléfono angosto no entran en una línea, y
      envolverlas en dos filas empuja el mapa fuera de la pantalla. */
.filtros-zona {
  display: flex; gap: 7px; margin-bottom: 10px;
  overflow-x: auto; scrollbar-width: none; -webkit-overflow-scrolling: touch;
}
.filtros-zona::-webkit-scrollbar { display: none; }
.filtro-z {
  flex-shrink: 0; padding: 7px 13px; border-radius: 99px; cursor: pointer;
  border: 1.5px solid var(--pin-line); background: var(--pin-surface);
  font: inherit; font-size: 13px; color: var(--pin-text-muted); white-space: nowrap;
}
.filtro-z.activo { background: var(--pin-accent); border-color: var(--pin-accent); color: #fff; font-weight: 600; }
/* El puntito marca las zonas asignadas a quien está mirando. No las pone
   primero ni esconde las demás: el orden es geográfico, de norte a sur, porque
   así es la costa y así la recorre el inspector. */
.filtro-z .mia {
  display: inline-block; width: 5px; height: 5px; border-radius: 50%;
  background: var(--pin-accent); margin-right: 5px; vertical-align: 1px;
}
.filtro-z.activo .mia { background: rgba(255, 255, 255, .85); }

/* ── Estado del GPS ─────────────────────────────────────────────────────── */
.gps {
  display: flex; align-items: center; gap: 12px; padding: 14px 16px;
  background: var(--pin-bg-suave); border: 1px solid var(--pin-line);
  border-radius: var(--pin-r-md); margin-bottom: 14px;
}
/* Va pegada a la tarjeta de la jornada, no suelta debajo: es una aclaración
   de ese interruptor, no un aviso más de la pantalla. */
.gps-nota {
  margin: -6px 2px 14px; font-size: 12.5px; line-height: 1.45;
  color: var(--pin-text-muted); text-wrap: pretty;
}
.gps-nota a { color: var(--pin-accent); font-weight: 600; }
.gps .ic { color: var(--pin-accent); display: flex; flex-shrink: 0; }
.gps .ic svg { width: 22px; height: 22px; }
.gps b { display: block; font-size: 14px; font-weight: 600; }
.gps small { display: block; font-size: 12.5px; color: var(--pin-text-muted); }
.gps.off .ic { color: var(--pin-text-faint); }

/* Interruptor de jornada */
.interruptor { margin-left: auto; flex-shrink: 0; position: relative; width: 52px; height: 30px; }
.interruptor input { position: absolute; opacity: 0; width: 100%; height: 100%; margin: 0; cursor: pointer; z-index: 2; }
.interruptor .pista {
  position: absolute; inset: 0; border-radius: 99px; background: rgba(51,51,51,.22);
  transition: background var(--pin-t);
}
.interruptor .pista::after {
  content: ''; position: absolute; top: 3px; left: 3px; width: 24px; height: 24px;
  border-radius: 50%; background: #fff; box-shadow: 0 2px 6px rgba(0,0,0,.25);
  transition: transform var(--pin-t) var(--pin-ease);
}
.interruptor input:checked + .pista { background: var(--pin-accent); }
.interruptor input:checked + .pista::after { transform: translateX(22px); }

/* ════════════════════════════════════════════════════════════════════════════
   INSPECTOR — escáner, mapa, resultado
   ════════════════════════════════════════════════════════════════════════════ */
.tabs {
  display: flex; position: fixed; left: 0; right: 0; bottom: 0; z-index: 60;
  background: var(--pin-bg); border-top: 1px solid var(--pin-line);
  padding: 8px 6px calc(8px + env(safe-area-inset-bottom, 0px));
}
.tab {
  flex: 1 1 0; min-width: 0; display: flex; flex-direction: column; align-items: center;
  justify-content: center; gap: 3px; padding: 4px 0; cursor: pointer;
  background: none; border: 0;
  /* 🔴 En NEGRO, no en gris. Esta barra se lee al sol, en la playa, con el
     brillo peleando: el gris tenue que queda bien en una oficina ahí
     directamente no se ve. El estado seleccionado ya se distingue por el verde,
     la burbuja y el tramo de línea de arriba — no hace falta además apagar los
     otros. */
  color: var(--pin-text); font-size: 10.5px; font-weight: 500;
}
.tab .ic {
  width: 50px; height: 26px; display: flex; align-items: center; justify-content: center;
  border-radius: 99px; transition: background var(--pin-t);
}
.tab .ic svg { width: 21px; height: 21px; }
.tab.activa { color: var(--pin-accent); }
.tab.activa .ic { background: var(--pin-accent-dim); }

.panel { display: none; }
.panel.activo { display: block; }

/* Escáner */
.escaner { position: relative; background: #04150c; border-radius: var(--pin-r-md); overflow: hidden; aspect-ratio: 1; }
.escaner video {
  width: 100%; height: 100%; object-fit: cover; display: block;
  /* 🔴 Tapado hasta que haya imagen. Un `<video>` sin stream no está vacío: el
     WebView de Android le dibuja encima su botón de play, un triángulo gris a
     pantalla completa. Se destapa desde `inspector.js` al empezar a reproducir. */
  opacity: 0; transition: opacity .28s ease;
}
.escaner.listo video { opacity: 1; }
.escaner-abriendo {
  position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  color: rgba(255, 255, 255, .72); font-size: 13.5px; letter-spacing: .01em;
}
.escaner.listo .escaner-abriendo { display: none; }
.escaner-mira {
  position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  pointer-events: none;
}
.escaner-mira i {
  width: 62%; aspect-ratio: 1; border-radius: 18px;
  box-shadow: 0 0 0 100vmax rgba(0,0,0,.5);
  border: 2px solid rgba(255,255,255,.5);
  position: relative;
}
/* Línea de barrido: le dice al inspector que la cámara está viva aunque todavía
   no haya encontrado nada. Sin esto parece colgada. */
.escaner-mira i::after {
  content: ''; position: absolute; left: 6%; right: 6%; height: 2px;
  background: linear-gradient(90deg, transparent, #4ade80, transparent);
  animation: barrido 2.4s ease-in-out infinite;
}
@keyframes barrido { 0%,100% { top: 8%; } 50% { top: 90%; } }

/* Resultado del control */
.resultado {
  border-radius: var(--pin-r-lg); padding: 22px 20px; margin-bottom: 16px;
  border: 2px solid; text-align: center;
}
.resultado.valido   { background: var(--pin-accent-dim); border-color: var(--pin-accent); }
.resultado.invalido { background: rgba(190, 18, 60, .06); border-color: var(--pin-danger); }
.resultado .sello {
  width: 68px; height: 68px; border-radius: 50%; margin: 0 auto 14px;
  display: flex; align-items: center; justify-content: center; color: #fff;
}
.resultado.valido   .sello { background: var(--pin-accent); }
.resultado.invalido .sello { background: var(--pin-danger); }
.resultado .sello svg { width: 38px; height: 38px; }
.resultado h3 { margin: 0 0 4px; font-size: 21px; font-weight: 700; letter-spacing: -.01em; }
.resultado.valido   h3 { color: #04503a; }
.resultado.invalido h3 { color: #9f1239; }
.resultado .motivo { margin: 0 0 16px; font-size: 14.5px; color: var(--pin-text); }
.resultado .rostro {
  width: 104px; height: 104px; border-radius: var(--pin-r-md); object-fit: cover;
  margin: 0 auto 14px; display: block; border: 2px solid #fff;
  box-shadow: var(--pin-shadow-card);
}
.resultado dl { text-align: left; margin: 0; background: rgba(255,255,255,.6); border-radius: var(--pin-r-md); padding: 4px 14px; }

/* Mapa */
/* ── Alto del mapa ─────────────────────────────────────────
   🔴 El mapa toma LO QUE SOBRA, no una altura calculada a mano. Antes era
      `100dvh - barra - 118px`, y ese 118 contaba la cabecera y el título del
      panel. Al sumar arriba la fila de filtros por tramo de costa nadie
      actualizó el número: el mapa quedó 44 px más alto que el hueco y se comió
      la barra de pestañas de abajo — en un teléfono, el inspector perdía el
      acceso al escáner y a la consulta por DNI.
      Con `flex: 1` el mapa se achica solo cuando se agrega algo encima. */
/* 🔴 EL ALTO DEL MAPA NO SE CALCULA RESTANDO CHROME A OJO. Antes era
   `100dvh - barra - 92px`, y esos 92 px tenían que cubrir la cabecera, el
   padding del cuerpo, la fila del título y la de los filtros. No alcanzaban:
   el panel quedaba más alto que la pantalla, el cuerpo crecía y el mapa
   terminaba pisando la barra de pestañas. Y cada vez que se suma una fila
   —los filtros por zona fueron una— la constante queda vieja otra vez.

   Ahora el mapa mide una FRACCIÓN de la ventana y el resto del panel ocupa lo
   que necesite. `clamp` lo sostiene en los dos extremos: no se vuelve inusable
   en un teléfono chico ni desproporcionado en una tablet. */
#p-mapa.activo { display: flex; flex-direction: column; }
#p-mapa > div:first-child, .filtros-zona { flex: 0 0 auto; }
#mapa {
  width: 100%; flex: 0 0 auto;
  height: clamp(200px, 52dvh, 600px);
  border-radius: var(--pin-r-md); overflow: hidden;
}
.leaflet-container { font-family: var(--pin-font); background: var(--pin-bg-suave); }
/* Pin propio: los iconos por defecto de Leaflet son PNG externos y con la CSP
   cerrada no cargarían. Se dibuja con CSS y no depende de ningún archivo. */
.pin-vendedor {
  width: 34px; height: 34px; border-radius: 50% 50% 50% 4px;
  transform: rotate(-45deg);
  background: var(--pin-accent); border: 2.5px solid #fff;
  box-shadow: 0 3px 10px rgba(0,0,0,.35);
  display: flex; align-items: center; justify-content: center;
}
.pin-vendedor b {
  transform: rotate(45deg); color: #fff; font-size: 13px; font-weight: 700;
  font-family: var(--pin-font-mono);
}
.pin-vendedor.frio { background: #94a3b8; }   /* sin reportar hace rato */
.pin-yo { width: 18px; height: 18px; border-radius: 50%; background: var(--pin-info); border: 3px solid #fff; box-shadow: 0 2px 8px rgba(0,0,0,.35); }

.leaflet-popup-content { margin: 12px 14px; font-size: 13.5px; }
.leaflet-popup-content b { display: block; font-size: 15px; margin-bottom: 3px; }
.leaflet-popup-content .m { font-family: var(--pin-font-mono); color: var(--pin-text-muted); font-size: 12.5px; }

/* ════════════════════════════════════════════════════════════════════════════
   LISTAS Y TABLAS (cola de revisión, padrón)
   ════════════════════════════════════════════════════════════════════════════ */
.lista { display: flex; flex-direction: column; gap: 10px; }
.item {
  display: flex; align-items: center; gap: 13px; padding: 14px 15px;
  background: var(--pin-surface); border: 1px solid var(--pin-line);
  border-radius: var(--pin-r-md); text-align: left; width: 100%; cursor: pointer;
}
.item:active { background: var(--pin-bg-suave); }
/* 🔴 `.item` tiene que servir también como ENLACE. Se usó como `<a>` por primera
   vez en los ajustes de la cuenta y salió celeste y subrayado, con el chevron a
   tamaño natural: la clase daba por sentado un `<button>` o un `<div>`, así que
   el `<a>` se quedaba con lo que trae el navegador de fábrica. No es un arreglo
   de esa pantalla — es el componente, y le pasaba a cualquier enlace futuro. */
a.item { color: var(--pin-text); text-decoration: none; }
/* El chevron del final: hijo DIRECTO, para no pisar los íconos que van adentro
   del contenido. `margin-left:auto` lo empuja al borde sin necesitar un `.txt`
   con `flex:1`, que es lo que se olvida al escribir el markup. */
.item > svg { width: 20px; height: 20px; flex-shrink: 0; margin-left: auto; color: var(--pin-text-faint); }
.item .avatar {
  width: 44px; height: 44px; border-radius: var(--pin-r-sm); flex-shrink: 0;
  background: var(--pin-accent-dim); color: var(--pin-accent);
  display: flex; align-items: center; justify-content: center;
  font-weight: 700; font-family: var(--pin-font-mono); font-size: 15px;
  object-fit: cover;
}
.item .txt { flex: 1; min-width: 0; }
.item b { display: block; font-size: 15px; font-weight: 600; line-height: 1.25; }
.item small { display: block; font-size: 12.5px; color: var(--pin-text-muted); font-family: var(--pin-font-mono); }

/* ── Legajo del art. 5 ─────────────────────────────────────────────────────
   Casi todo el legajo se pinta con lo que ya había: `.item` para cada asiento,
   `.chip` para su estado, `.dato` para las fichas, `.aviso` para el semáforo.
   Lo único que no existía es el medidor del Anexo II, porque no había en toda
   la app nada que mostrara "cuánto de cuánto".

   🔴 El medidor es deliberadamente sobrio: sin verde de "aprobado" ni rojo de
      "rechazado". El Anexo II produce un ORDEN DE MÉRITO, no un aprobado: 40
      puntos ganan si nadie sacó más. Pintarlo como un semáforo le haría creer
      al evaluador que el sistema ya decidió, y decide el art. 23. */
.medidor { margin: 6px 0 2px; }
.medidor .cifra {
  display: flex; align-items: baseline; gap: 8px;
  font-family: var(--pin-font-mono); margin-bottom: 10px;
}
.medidor .cifra b { font-size: 34px; font-weight: 600; line-height: 1; letter-spacing: -.02em; }
.medidor .cifra span { font-size: 13px; color: var(--pin-text-muted); }
.medidor .pista {
  height: 8px; border-radius: 99px; background: var(--pin-bg-suave);
  overflow: hidden; border: 1px solid var(--pin-line);
}
.medidor .pista i { display: block; height: 100%; background: var(--pin-accent); border-radius: 99px; }

/* Cada criterio con su fundamento debajo: sin el porqué a la vista, un número
   es una orden. Los que suman cero quedan atenuados, no tachados — que no
   sumen hoy no significa que estén mal. */
.criterio {
  display: flex; align-items: flex-start; gap: 12px;
  padding: 11px 0; border-bottom: 1px solid var(--pin-line);
}
.criterio:last-child { border-bottom: 0; }
.criterio .pts {
  font-family: var(--pin-font-mono); font-size: 14px; font-weight: 600;
  min-width: 52px; text-align: right; flex-shrink: 0; color: var(--pin-accent);
}
.criterio.cero .pts { color: var(--pin-text-faint); }
.criterio .txt { flex: 1; min-width: 0; }
.criterio .txt b { display: block; font-size: 14px; font-weight: 600; }
.criterio .txt small { display: block; font-size: 12.5px; color: var(--pin-text-muted); margin-top: 2px; }
.criterio.cero .txt b { color: var(--pin-text-muted); font-weight: 500; }

.tabla-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; margin: 0 -16px; padding: 0 16px; }
table.padron { width: 100%; border-collapse: collapse; font-size: 14px; min-width: 620px; }
table.padron th {
  text-align: left; font-size: 10.5px; letter-spacing: .08em; text-transform: uppercase;
  color: var(--pin-text-muted); font-weight: 600; padding: 10px 12px;
  border-bottom: 1.5px solid var(--pin-line); white-space: nowrap;
}
table.padron td { padding: 11px 12px; border-bottom: 1px solid var(--pin-line); vertical-align: middle; }
table.padron td.m { font-family: var(--pin-font-mono); font-size: 13px; }
table.padron tr:last-child td { border-bottom: 0; }

/* ── Revisión de una verificación ───────────────────────────────────────── */
.evidencia { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 12px; margin-bottom: 18px; }
.evidencia figure { margin: 0; }
.evidencia img, .evidencia video {
  width: 100%; border-radius: var(--pin-r-sm); border: 1px solid var(--pin-line);
  display: block; background: var(--pin-bg-suave); cursor: zoom-in;
}
.evidencia figcaption {
  font-size: 11px; color: var(--pin-text-muted); margin-top: 5px;
  font-family: var(--pin-font-mono); letter-spacing: .05em; text-transform: uppercase;
}
.senales { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 18px; }

/* ── Ampliación suave de imágenes ─────────────────────────────────────────
   La imagen VIAJA desde donde está hasta el centro de la pantalla, con la
   misma técnica que la transición del logo en el splash: se miden las dos
   posiciones y se anima un `transform`, lo único que el navegador puede mover
   sin recalcular el layout en cada cuadro.

   Que aparezca de golpe se lee como un cambio de pantalla; que viaje se lee
   como acercarse — y acá el gesto ES acercarse a mirar una cara o el número de
   un documento. Ver `App.zoomFoto`. */
.visor { position: fixed; inset: 0; z-index: 4500; display: none; background: rgba(4, 21, 12, 0); }
.visor.abierto { display: block; }
.visor.viendo { background: rgba(4, 21, 12, .94); transition: background .32s ease; }
.visor img {
  position: fixed; margin: 0; border-radius: 16px; object-fit: cover;
  transform-origin: top left; will-change: transform;
  box-shadow: 0 24px 60px rgba(0, 0, 0, .5);
}
.visor.animando img {
  transition: transform .42s cubic-bezier(.22, 1, .36, 1), border-radius .42s ease;
}
.visor .cerrar {
  position: absolute; top: calc(14px + env(safe-area-inset-top, 0px)); right: 14px;
  width: 44px; height: 44px; border-radius: 50%; border: 0; cursor: pointer;
  background: rgba(255, 255, 255, .16); color: #fff;
  display: grid; place-items: center; opacity: 0; transition: opacity .3s ease;
}
.visor.viendo .cerrar { opacity: 1; }
.visor .pie-visor {
  position: absolute; left: 0; right: 0;
  bottom: calc(26px + env(safe-area-inset-bottom, 0px));
  text-align: center; color: rgba(255, 255, 255, .72); font-size: 13.5px;
  padding: 0 24px; margin: 0; opacity: 0; transition: opacity .3s ease .1s;
}
.visor.viendo .pie-visor { opacity: 1; }

/* Señal de que la imagen se puede ampliar: sin esto se lee como decoración. */
.ampliable { position: relative; border: 0; padding: 0; background: none; cursor: zoom-in; line-height: 0; }
.ampliable .lupa-ic {
  position: absolute; right: -5px; bottom: -5px; width: 26px; height: 26px;
  border-radius: 50%; background: var(--pin-accent); color: #fff;
  display: grid; place-items: center; box-shadow: 0 2px 8px rgba(18, 53, 36, .28);
}
.ampliable .lupa-ic svg { width: 13px; height: 13px; }

/* Lupa: la foto del DNI a tamaño real es indispensable para leer el número. */
.lupa {
  position: fixed; inset: 0; z-index: 3000; background: rgba(4, 21, 12, .94);
  display: none; align-items: center; justify-content: center; padding: 20px;
}
.lupa.abierta { display: flex; }
.lupa img, .lupa video { max-width: 100%; max-height: 100%; border-radius: var(--pin-r-sm); }
.lupa .cerrar {
  position: absolute; top: calc(16px + env(safe-area-inset-top, 0px)); right: 16px;
  width: 44px; height: 44px; border-radius: 50%; border: 0; cursor: pointer;
  background: rgba(255,255,255,.16); color: #fff; display: flex; align-items: center; justify-content: center;
}

/* ── Vacíos y cargando ──────────────────────────────────────────────────── */
.vacio { text-align: center; padding: 48px 20px; color: var(--pin-text-muted); }
.vacio svg { width: 46px; height: 46px; color: var(--pin-text-faint); margin-bottom: 12px; }
.vacio b { display: block; font-size: 16px; color: var(--pin-text); margin-bottom: 4px; }
.vacio p { margin: 0; font-size: 14px; }

.girando { animation: girar 1s linear infinite; }
@keyframes girar { to { transform: rotate(360deg); } }

.cargando {
  position: fixed; inset: 0; z-index: 4000; background: rgba(255,255,255,.9);
  display: none; align-items: center; justify-content: center; flex-direction: column; gap: 14px;
}
.cargando.visible { display: flex; }
.cargando svg { width: 34px; height: 34px; color: var(--pin-accent); }
.cargando span { font-size: 14px; color: var(--pin-text-muted); }

/* ── Mensajes flotantes ─────────────────────────────────────────────────── */
/* 🔴 Una sola línea por defecto.
   `text-wrap: balance` acá era contraproducente: reparte los renglones parejos
   UNA VEZ QUE el texto ya se partió, pero no impide que se parta. Un aviso
   corto cortado en dos se lee como un error de maquetado.
   Los mensajes largos —errores del servidor, sobre todo— sí necesitan varias
   líneas, y para esos `App.brindis` agrega la clase `multilinea`. */
#brindis {
  position: fixed; left: 50%; z-index: 5000;
  bottom: calc(var(--barra-h) + 18px); transform: translateX(-50%) translateY(20px);
  background: #04150c; color: #fff; padding: 13px 18px; border-radius: 99px;
  font-size: 14px; max-width: 94vw; text-align: center;
  white-space: nowrap;
  opacity: 0; pointer-events: none; transition: opacity var(--pin-t), transform var(--pin-t) var(--pin-ease);
  box-shadow: 0 10px 34px rgba(0,0,0,.3);
}
#brindis.multilinea {
  white-space: normal; text-wrap: pretty;
  border-radius: var(--pin-r-lg);       /* la píldora no cierra bien en 2 líneas */
  max-width: min(92vw, 420px);
}
#brindis.visible { opacity: 1; transform: translateX(-50%) translateY(0); }
#brindis.malo { background: var(--pin-danger); }

/* ── Cartel de actualización disponible ──────────────────────────────────
   Lo inyecta `version-guard.js`. Va abajo para no tapar la cabecera ni el QR,
   y por encima de la barra de pestañas del inspector (de ahí --barra-h).
   Sin backdrop-filter: es un elemento fijo con toda la página scrolleando por
   detrás, que es el caso exacto de la regla de oro heredada del panel de Datos. */
#va-update {
  position: fixed; left: 12px; right: 12px; z-index: 5500;
  bottom: calc(var(--barra-h) + 14px);
  margin: 0 auto; max-width: 460px;
  display: flex; align-items: center; gap: 14px;
  padding: 13px 14px 13px 18px;
  background: #04150c; color: #eafff2;
  border: 1px solid rgba(0, 146, 90, .55); border-radius: var(--pin-r-pill);
  box-shadow: 0 12px 34px rgba(0, 0, 0, .34);
  opacity: 0; transform: translateY(16px);
  transition: opacity var(--pin-t), transform var(--pin-t) var(--pin-ease);
}
#va-update.visible { opacity: 1; transform: translateY(0); }
#va-update.oculto  { opacity: 0; transform: translateY(16px); pointer-events: none; }
.va-update-txt { flex: 1; min-width: 0; font-size: 14px; line-height: 1.35; }

/* ── Bloqueo por cáscara vencida ───────────────────────────────────────────
   Pantalla completa y sin salida, a propósito. Se muestra sólo cuando el APK
   instalado quedó por debajo del mínimo: seguir dejándolo andar le haría creer
   al vendedor que está reportando su posición cuando no la está reportando, y
   eso es peor que frenarlo. Es la única pantalla de toda la app que no tiene
   forma de cerrarse. */
#va-apk {
  position: fixed; inset: 0; z-index: 9999;
  background: var(--pin-bg); display: grid; place-items: center;
  padding: 28px; opacity: 0; transition: opacity var(--pin-t);
}
#va-apk.visible { opacity: 1; }
#va-apk .va-apk-caja { max-width: 380px; text-align: center; }
#va-apk img { width: 62px; height: auto; margin: 0 auto 22px; display: block; }
#va-apk b { display: block; font-size: 20px; font-weight: 600; margin-bottom: 10px; }
#va-apk p { font-size: 15px; color: var(--pin-text-muted); margin: 0 0 14px; text-wrap: pretty; }
#va-apk small { display: block; font-size: 13px; color: var(--pin-text-faint); margin-bottom: 22px; }
#va-apk .btn { display: inline-flex; text-decoration: none; }

/* El cartel de descarga reusa `#va-update`, pero ahí el botón es un <a>. */
#va-update a#va-actualizar { text-decoration: none; display: inline-flex; align-items: center; }

#va-actualizar {
  flex-shrink: 0; cursor: pointer;
  background: var(--pin-accent-soft); color: #04140a;
  border: 0; border-radius: var(--pin-r-pill);
  padding: 9px 18px; font-size: 14px; font-weight: 700; white-space: nowrap;
}
#va-actualizar:active { transform: scale(.97); }
#va-actualizar:disabled { opacity: .6; cursor: default; }

/* ════════════════════════════════════════════════════════════════════════════
   ESCRITORIO — el panel de administración es lo único que se usa en una PC
   ════════════════════════════════════════════════════════════════════════════ */
@media (min-width: 900px) {
  .cuerpo { padding: 28px 32px calc(32px + env(safe-area-inset-bottom, 0px)); }
  .centrado { max-width: 560px; }
  #v-admin .centrado { max-width: 1100px; }
  #v-inspector .centrado { max-width: 940px; }
  .tabs { max-width: 940px; margin: 0 auto; border-radius: var(--pin-r-lg) var(--pin-r-lg) 0 0; border: 1px solid var(--pin-line); border-bottom: 0; }
  /* En escritorio el panel es más alto porque el cuerpo tiene más aire arriba.
     Se ajusta el CONTENEDOR, no el mapa: el mapa sigue tomando lo que sobre. */
  /* En escritorio hay más alto disponible y no hay barra de pestañas abajo. */
  #mapa { height: clamp(280px, 62dvh, 720px); }
}


/* ── Código del SMS ────────────────────────────────────────────────────────
   Seis dígitos, separados y grandes: se tipean mirando el SMS y al sol. La
   fuente mono es la misma que el resto de los datos duros (DNI, permisos). */
#sms-codigo {
  font-family: var(--pin-font-mono);
  font-size: 26px;
  letter-spacing: .38em;
  text-align: center;
  padding-left: .38em;          /* compensa el espaciado del último dígito */
}
/* El contenedor del reCAPTCHA invisible no ocupa lugar, pero si el desafío
   aparece tiene que poder verse: por eso no se lo esconde con display:none. */
#sms-recaptcha { margin: 18px 0 0; display: flex; justify-content: center; }


/* ── «La cara no coincide» ─────────────────────────────────────────────────
   ⚠️ Discreto pero encontrable. No puede competir con «Escanear otro», que es
      lo que se toca cien veces por jornada, y tampoco puede estar escondido:
      es la única forma de dejar registro de un puesto revendido. Por eso no
      lleva cuerpo de botón hasta que se lo toca. */
.btn-cotejo {
  display: block;
  width: 100%;
  margin: 14px 0 0;
  padding: 11px 14px;
  border: 1px solid var(--pin-danger);
  border-radius: var(--pin-r-sm, 10px);
  background: transparent;
  color: var(--pin-danger);
  font: 600 14px/1.2 var(--pin-font);
  cursor: pointer;
}
.btn-cotejo:active { background: rgba(190, 30, 45, .08); }

.cotejo-botones { display: flex; gap: 10px; margin: 12px 0 0; }
.cotejo-botones .btn { margin: 0; flex: 1 1 0; }

/* El paso irreversible va en rojo: asentar que alguien exhibe un permiso ajeno
   es el supuesto del art. 32 inc. d, no un tilde más. */
.btn.peligro { background: var(--pin-danger); border-color: var(--pin-danger); color: #fff; }


/* ── El escáner en reposo ──────────────────────────────────────────────────
   🔴 La pestaña abre así y NO con la cámara prendida. Entrar a la pestaña no es
      querer escanear: se entra para mirar el mapa, para consultar por DNI, o
      volviendo de otra pantalla. El botón va abajo al centro, donde cae el
      pulgar y donde lo ponen las billeteras. */
.escaner-inicio {
  text-align: center;
  padding: 26px 16px 0;
}
.escaner-arte {
  width: 96px; height: 96px; margin: 0 auto 18px;
  display: flex; align-items: center; justify-content: center;
  border-radius: 50%;
  background: var(--pin-accent-dim);
  color: var(--pin-accent);
}
.escaner-arte svg { width: 46px; height: 46px; }
.escaner-inicio b { display: block; font-size: 17px; margin: 0 0 6px; }
.escaner-inicio span {
  display: block; max-width: 30ch; margin: 0 auto;
  font-size: 14px; line-height: 1.45; color: var(--pin-text-muted);
}

/* ⚠️ `margin-top:auto` y no una altura fija: el panel es flexible y la barra de
      pestañas cambia de alto entre teléfonos. Empujado desde abajo, el botón
      queda siempre a la misma distancia del pulgar. */
.escaner-accion {
  margin-top: auto;
  padding: 34px 0 10px;
  display: flex; flex-direction: column; align-items: center; gap: 10px;
}
.escaner-boton {
  width: 78px; height: 78px; border-radius: 50%;
  border: 0; cursor: pointer;
  background: var(--pin-accent); color: #fff;
  display: flex; align-items: center; justify-content: center;
  box-shadow: 0 8px 22px rgba(0, 114, 66, .32);
  transition: transform .12s ease, box-shadow .12s ease;
}
.escaner-boton svg { width: 34px; height: 34px; }
.escaner-boton:active { transform: scale(.94); box-shadow: 0 4px 12px rgba(0, 114, 66, .28); }
.escaner-accion small {
  font-size: 13px; font-weight: 600; letter-spacing: .02em;
  color: var(--pin-text-muted);
}

/* El panel tiene que poder empujar el botón hacia abajo. */
#p-escanear.activo { display: flex; flex-direction: column; min-height: calc(100dvh - var(--barra-h) - 150px); }
#p-escanear > #escaner-zona { display: flex; flex-direction: column; flex: 1 1 auto; }

/* ── Botón central de la botonera ──────────────────────────────────────────
   🔴 Igual que las billeteras: la acción que se hace cien veces por jornada va
      en un botón redondo, más grande que el resto y levantado sobre la barra,
      en el centro exacto donde cae el pulgar. Para el inspector es «Escanear
      QR»; para el vendedor, «Mi QR». Antes el escáner era una pestaña más entre
      cuatro iguales y la acción principal no se distinguía de las secundarias.

   ⚠️ Es un `.tab` de verdad, no un flotante aparte: comparte el mismo estado
      `.activa` y el mismo manejador. Un botón por fuera del sistema de pestañas
      terminaría desincronizado con el panel que muestra. */
.tab-centro {
  position: relative;
  flex: 0 0 auto;
  width: 76px;
  /* ⚰️ Acá decía `--pin-text-faint`, y contradecía el comentario de `.tab` tres
     reglas más arriba: «EN NEGRO, no en gris. Esta barra se lee al sol». El
     rótulo de la acción PRINCIPAL —«Mi QR», «Escanear»— era el único apagado de
     la fila, justo el que más hay que poder leer. */
  color: var(--pin-text);
}
.tab-centro .ic {
  position: absolute;
  top: -26px; left: 50%; transform: translateX(-50%);
  width: 58px; height: 58px; border-radius: 50%;
  background: var(--pin-accent); color: #fff;
  box-shadow: 0 6px 18px rgba(0, 114, 66, .34);
  transition: transform .12s ease, box-shadow .12s ease;
}
.tab-centro .ic svg { width: 27px; height: 27px; }
.tab-centro:active .ic { transform: translateX(-50%) scale(.93); }
/* El rótulo baja para no chocar con el círculo levantado. */
.tab-centro > span:not(.ic) { margin-top: 30px; }
.tab-centro.activa { color: var(--pin-accent); }
.tab-centro.activa .ic { background: var(--pin-accent); }

/* La barra necesita aire arriba para que el círculo no se corte. */
.tabs { overflow: visible; }

/* ── Tarjeta del clima en la pantalla de inicio ────────────────────────────
   🔴 El clima estaba suelto adentro del menú lateral: había que abrir un cajón
      para ver si había viento. Para quien trabaja parado en la arena ocho horas
      eso es el dato del día, no un extra — va en la primera pantalla. */
.clima-hoy {
  display: flex; align-items: center; gap: 16px;
  padding: 18px 18px;
  border-radius: var(--pin-r-md);
  background: linear-gradient(135deg, var(--pin-accent) 0%, #00915a 100%);
  color: #fff;
}
.clima-hoy .dibujo { width: 62px; height: 62px; flex: 0 0 auto; }
.clima-hoy .dibujo svg { width: 100%; height: 100%; }
.clima-hoy .datos { flex: 1 1 auto; min-width: 0; }
.clima-hoy .grande { font: 700 30px/1 var(--pin-font); letter-spacing: -.02em; }
.clima-hoy .cielo { display: block; margin: 4px 0 0; font-size: 13.5px; opacity: .92; }
.clima-hoy .renglon {
  display: flex; gap: 14px; margin: 10px 0 0; font-size: 12.5px; opacity: .95;
  flex-wrap: wrap;
}
.clima-hoy .renglon b { font-weight: 600; }
.clima-cargando { padding: 22px; text-align: center; color: var(--pin-text-muted); font-size: 14px; }

/* ── Acordeón de teléfonos útiles ──────────────────────────────────────────
   ⚠️ Los teléfonos estaban sueltos, uno abajo del otro, y estiraban el menú
      hasta obligar a scrollear para llegar a cerrar sesión. Plegados, el menú
      entra en una pantalla y el que necesita un teléfono lo despliega. */
.plegable > summary {
  list-style: none; cursor: pointer;
  display: flex; align-items: center; gap: 12px;
  padding: 14px 4px;
  font-size: 15px; font-weight: 600;
}
.plegable > summary::-webkit-details-marker { display: none; }
.plegable > summary .flecha {
  margin-left: auto; transition: transform var(--pin-t);
  display: flex; color: var(--pin-text-faint);
}
.plegable[open] > summary .flecha { transform: rotate(90deg); }
.plegable > summary svg { width: 20px; height: 20px; }

/* ── Veredicto del control: foto y respuesta en el mismo renglón ───────────
   🔴 El inspector está parado en la vereda con la persona adelante. Lo que
      decide —la cara, el nombre y si está autorizado— tiene que entrar en la
      primera pantalla, sin deslizar. Antes la foto iba centrada arriba del
      sello y empujaba el veredicto fuera de la vista. */
.veredicto { display: flex; align-items: center; gap: 16px; text-align: left; }
.veredicto .ampliable { flex: 0 0 auto; margin: 0; }
.veredicto .rostro { width: 92px; height: 92px; margin: 0; }
.veredicto .sello { flex: 0 0 auto; margin: 0; }
.veredicto-txt { flex: 1 1 auto; min-width: 0; }
.veredicto-txt h3 { margin: 0; }
.veredicto-txt .nombre {
  margin: 4px 0 0; font-size: 16px; font-weight: 600; color: var(--pin-text);
  overflow-wrap: anywhere;
}
.veredicto-txt .motivo { margin: 2px 0 0; text-align: left; }
.cotejo-pista {
  font-size: 12.5px; color: var(--pin-text-muted);
  margin: 14px 0 0; text-align: center;
}


/* ── Logo institucional en la cabecera ─────────────────────────────────────
   🔴 Antes iba el escudo chico y al lado el nombre de quien estaba usando la
      app. Es al revés: la persona ya sabe quién es —lo que tiene que reconocer
      de un vistazo es que esto es la Municipalidad—. Sus datos se mudaron al
      panel lateral, que es donde se va a mirarlos. */
/* 🔴 CENTRADO Y EN POSICIÓN ABSOLUTA. Es la marca institucional, no un dato
   más de la fila: a la izquierda competía con los botones y quedaba
   descolgada. Absoluta se centra respecto de la cabecera ENTERA, así que no se
   corre cuando aparece o desaparece el engranaje de administración — que es
   justo lo que pasaría centrándola con flex.
   ⚠️ `pointer-events: none` para que no le robe el toque a los botones que
      quedan debajo de su caja. */
.marca-logo {
  /* ⚠️ CENTRADO ÓPTICO, no geométrico. El logo es el escudo a la izquierda y el
     texto a la derecha: centrando la imagen por su caja, el escudo —que es una
     mancha densa y compacta— tira del peso visual y el TEXTO, que es lo que se
     lee, queda corrido a la derecha. Los 6 px lo devuelven al eje. Es el mismo
     criterio con el que se centra un logotipo en una hoja: manda lo que el ojo
     ve como centro, no lo que mide la regla. */
  position: absolute; left: 50%; transform: translateX(calc(-50% - 6px));
  top: calc(11px + env(safe-area-inset-top, 0px));
  display: flex; align-items: center; height: 40px;
  pointer-events: none;
}
.marca-logo img { height: 100%; width: auto; display: block; }





/* 🔴 SE ACOTA POR EL ANCHO, NO POR EL ALTO. El logo mide 800 x 142 —casi 5,7
   veces más ancho que alto—, así que fijarle 46 px de alto lo estiraba a 260 px
   y ocupaba el cajón entero. Es una firma al pie, no un cartel: manda el ancho
   y el alto sale solo. */
/* La cuenta de Google vinculada: se dice CUÁL, no sólo que hay una. */
.menu-item .cuenta {
  display: block; margin-top: 3px;
  font-family: var(--pin-font-mono); font-size: 11px;
  color: var(--pin-accent);
  /* ⚠️ En UNA sola línea. Con `overflow-wrap: anywhere` un correo largo se
     partía a mitad de palabra en tres renglones y se leía como un error de
     maquetación. Cortado con puntos suspensivos se entiende que sigue; partido
     al medio, no. */
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* ── Fecha del día en la pantalla de inicio ────────────────────────────────
   Va sobre la tarjeta del clima: es el encabezado natural de «cómo está hoy». */
.inicio-fecha {
  margin: 2px 0 12px;
  font-size: 13px; letter-spacing: .02em;
  color: var(--pin-text-muted);
}
.inicio-fecha b {
  display: block; font-size: 21px; font-weight: 700; letter-spacing: -.01em;
  /* ⚰️ Acá había `text-transform: capitalize`, que capitaliza cada palabra:
     «Lunes, 24 De Agosto». Lo resuelve `fechaHoy()`, que sabe qué idioma es. */
  color: var(--pin-text); line-height: 1.2;
}

/* ── Botonera: sin destello azul y con la marca del seleccionado ───────────
   🔴 EL DESTELLO AZUL ES DEL NAVEGADOR, no nuestro: Chrome pinta un rectángulo
      celeste sobre cualquier cosa que se toca. En una app institucional verde
      aparece como un error de la aplicación. Se apaga en toda la app.

   🔴 Y la pestaña activa marca su tramo de la línea superior, más grueso y en
      verde. La burbuja sola dice «esto está seleccionado»; el tramo de línea
      dice además DÓNDE estás dentro de la barra, que es lo que se lee de reojo
      sin mirar. */
* { -webkit-tap-highlight-color: transparent; }

.tabs { position: fixed; }
.tab { position: relative; }
.tab::before {
  content: ''; position: absolute;
  top: -9px;                       /* sobre el borde de 1 px de la barra */
  left: 14%; right: 14%; height: 3px;
  border-radius: 0 0 3px 3px;
  background: transparent;
  transition: background var(--pin-t);
}
.tab.activa::before { background: var(--pin-accent); }
/* El del centro tiene el círculo levantado: la línea ahí sobra. */
.tab-centro::before { display: none; }


/* ── Confirmar el celular antes de gastar un SMS ───────────────────────────
   🔴 El número va grande y en mono, con los grupos separados: es el último
      momento en que un dígito cambiado se puede ver. Después ya no —el código
      le llega a un desconocido, la persona queda sin recuperación, y el mensaje
      se cobró igual. */
.sms-numero {
  font-family: var(--pin-font-mono);
  font-size: 25px; letter-spacing: .02em; text-align: center;
  color: var(--pin-text); margin: 6px 0 0; white-space: nowrap;
}
.sms-botones { display: flex; gap: 10px; margin: 20px 0 0; }
.sms-botones .btn { margin: 0; flex: 1 1 0; }


/* ── El botón del QR respira ───────────────────────────────────────────────
   ⚠️ Un halo que late lento y muy suave, no un parpadeo. Tiene que leerse como
      «esto está vivo y es lo principal», no como una alarma: el inspector lo
      mira cien veces por jornada y algo agresivo cansa a la tercera.
   ⚠️ Se apaga con `prefers-reduced-motion` — hay gente a la que el movimiento
      le molesta de verdad, y el botón funciona igual sin él. */
.tab-centro .ic::after {
  content: ''; position: absolute; inset: -6px;
  border-radius: 50%;
  border: 2px solid var(--pin-accent);
  opacity: 0;
  animation: halo-qr 2.8s var(--pin-ease) infinite;
  pointer-events: none;
}
@keyframes halo-qr {
  0%   { transform: scale(.86); opacity: 0; }
  35%  { opacity: .38; }
  70%  { transform: scale(1.18); opacity: 0; }
  100% { transform: scale(1.18); opacity: 0; }
}
/* El círculo tiene que poder contener al halo. */
.tab-centro .ic { position: absolute; overflow: visible; }
.tab-centro.activa .ic::after { animation-duration: 2.2s; }

@media (prefers-reduced-motion: reduce) {
  .tab-centro .ic::after { animation: none; }
}

/* ── La página «Más» ───────────────────────────────────────────────────────
   🔴 Era un cajón lateral y pasó a ser una PÁGINA. Un cajón tapa la aplicación
      y obliga a cerrarlo para seguir; como pestaña, la barra de abajo queda a
      la vista y se sale tocando cualquier otra. Es lo que hacen las billeteras
      con su botón «Más», y por la misma razón. */
.mas-quien {
  display: flex; align-items: center; gap: 14px;
  padding: 4px 2px 18px;
  border-bottom: 1px solid var(--pin-line);
  margin-bottom: 6px;
}
.mas-avatar {
  width: 48px; height: 48px; flex: 0 0 auto;
  display: flex; align-items: center; justify-content: center;
  border-radius: 50%; background: var(--pin-accent-dim); color: var(--pin-accent);
}
.mas-avatar svg { width: 26px; height: 26px; }
/* La foto de la credencial. `overflow:hidden` + `object-fit:cover` para que un
   retrato que no sea cuadrado se recorte centrado en vez de deformarse — la
   miniatura se genera a 400 px cuadrados, pero las de las cuentas viejas son de
   160 y conviene no depender de eso. */
.mas-avatar.con-foto { overflow: hidden; background: var(--pin-line); }
.mas-avatar.con-foto img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* El marco donde se muestra un documento del propio sitio (hoy, la política de
   privacidad). `flex:1` para que ocupe todo lo que queda bajo la cabecera: sin
   eso el iframe cae a su alto por omisión —150 px— y se ve una ventanita. */
.marco-doc { flex: 1; width: 100%; border: 0; display: block; background: var(--pin-bg); }
.mas-quien > span:not(.mas-avatar) { min-width: 0; }
.mas-quien b { display: block; font-size: 17px; font-weight: 600; line-height: 1.2; }
.mas-quien b + span {
  display: block; margin-top: 3px;
  font-size: 11px; letter-spacing: .09em; text-transform: uppercase;
  color: var(--pin-accent); font-weight: 600;
}

/* El pie con el logo, ahora dentro de la página y no del cajón. */
.menu-pie {
  padding: 26px 0 6px;
  border-top: 1px solid var(--pin-line);
  margin-top: 26px;
  display: flex; justify-content: center;
}
.menu-pie img { width: 190px; max-width: 72%; height: auto; opacity: .9; }

/* ── Volver desde un panel sin pestaña ─────────────────────────────────────
   🔴 El carro y el permiso se llegan desde «Más» y no tienen pestaña propia:
      sin una salida visible se entra y no hay forma de volver salvo tocar otra
      pestaña, que además pierde el lugar donde estabas. */
.volver-panel {
  display: inline-flex; align-items: center; gap: 4px;
  margin: 0 0 14px -6px; padding: 6px;
  background: none; border: 0; cursor: pointer;
  color: var(--pin-accent); font: 600 14.5px/1 var(--pin-font);
}
.volver-panel svg { width: 20px; height: 20px; }

/* ── El dibujo del clima se mueve ──────────────────────────────────────────
   ⚠️ Cada parte tiene su propia animación y todas son LENTAS. Un ícono que
      parpadea en la pantalla de inicio cansa a la tercera vez que se abre la
      app, y esto se abre muchas veces por jornada. El sol gira, las nubes se
      corren un poco, las gotas caen: lo justo para que se lea como el tiempo y
      no como un logo temblando.
   ⚠️ Todo se apaga con `prefers-reduced-motion`. */
.cl-rayos { transform-origin: 30px 26px; animation: cl-girar 26s linear infinite; }
.cl-nube  { animation: cl-flotar 7s ease-in-out infinite; }
.cl-gota  { animation: cl-caer 1.5s linear infinite; }
.cl-g2 { animation-delay: .5s; }
.cl-g3 { animation-delay: 1s; }
.cl-rayo  { animation: cl-destello 3.4s ease-in-out infinite; transform-origin: 34px 56px; }
.cl-niebla { animation: cl-flotar 5s ease-in-out infinite; }

@keyframes cl-girar  { to { transform: rotate(360deg); } }
@keyframes cl-flotar { 0%, 100% { transform: translateX(0); } 50% { transform: translateX(2.5px); } }
@keyframes cl-caer   { 0% { transform: translateY(-3px); opacity: 0; }
                       25% { opacity: 1; }
                       100% { transform: translateY(5px); opacity: 0; } }
@keyframes cl-destello { 0%, 82%, 100% { opacity: 1; } 88% { opacity: .25; } 94% { opacity: 1; } }

/* 🔴 LA LUNA NO SE ANIMA CON `transform`, y no es una preferencia estética: el
   grupo `.cl-luna` YA lleva un `transform` en el propio SVG —el translate y el
   scale que la ubican— y una animación de transform en CSS lo PISA, mandando el
   creciente a la esquina superior izquierda a escala 1. Compila, no avisa, y se
   ve como un dibujo roto. Por eso late en opacidad, que no compite con nada.
   ⚠️ Y late apenas: una luna que pulsa fuerte parece una alarma. */
.cl-luna { animation: cl-respirar 7s ease-in-out infinite; }
.cl-estrellas circle { animation: cl-titilar 4s ease-in-out infinite; }
.cl-estrellas circle:nth-child(2) { animation-delay: 1.3s; }
.cl-estrellas circle:nth-child(3) { animation-delay: 2.6s; }

/* Las tres ondas se mueven en sentidos opuestos y a velocidades distintas: tres
   idénticas desplazándose igual se leen como una sola cosa temblando. */
.cl-ola { animation: cl-vaiven 9s ease-in-out infinite; }
.cl-o2  { animation-duration: 6.5s; animation-direction: reverse; }
.cl-o3  { animation-duration: 11s; animation-delay: -2s; }

@keyframes cl-respirar { 0%, 100% { opacity: 1; } 50% { opacity: .78; } }
@keyframes cl-titilar  { 0%, 100% { opacity: .9; } 45% { opacity: .25; } }
@keyframes cl-vaiven   { 0%, 100% { transform: translateX(-7px); }
                         50%      { transform: translateX(7px); } }

@media (prefers-reduced-motion: reduce) {
  .cl-rayos, .cl-nube, .cl-gota, .cl-rayo, .cl-niebla,
  .cl-luna, .cl-estrellas circle, .cl-ola { animation: none; }
}

/* ── «Ver el pronóstico» ───────────────────────────────────────────────────
   ⚠️ Detrás de un toque y no en la tarjeta: lo que decide la jornada —viento,
      temperatura, olas— entra en cuatro renglones. El pronóstico por hora es
      para otra pregunta: si aguanta hasta las seis. */
.clima-mas {
  display: flex; align-items: center; justify-content: center; gap: 4px;
  width: 100%; margin: 10px 0 0; padding: 11px;
  background: none; border: 0; cursor: pointer;
  color: var(--pin-accent); font: 600 14px/1 var(--pin-font);
}
.clima-mas svg { width: 17px; height: 17px; }

/* ── El pronóstico ─────────────────────────────────────────────────────────
   🔴 Las horas van en una tira que se desliza, y los días en filas. Es la forma
      en que se leen: las horas se recorren de a una hacia adelante —«¿aguanta
      hasta las seis?»— y los días se comparan de un vistazo entre sí. */
.cl-horas {
  display: flex; gap: 4px; overflow-x: auto; -webkit-overflow-scrolling: touch;
  padding: 4px 0 10px; margin: 0 -16px; padding-left: 16px; padding-right: 16px;
  scrollbar-width: none;
}
.cl-horas::-webkit-scrollbar { display: none; }
.cl-hora {
  flex: 0 0 auto; width: 62px;
  display: flex; flex-direction: column; align-items: center; gap: 5px;
  padding: 12px 4px; border-radius: var(--pin-r-sm);
  background: var(--pin-bg-suave);
}
.cl-hora .cl-h { font-size: 11.5px; color: var(--pin-text-muted); font-weight: 600; }
.cl-hora .cl-ic { width: 34px; height: 34px; display: block; }
.cl-hora .cl-t { font: 700 17px/1 var(--pin-font); }
.cl-hora .cl-p { font-size: 11px; color: var(--pin-info); font-weight: 600; }
.cl-ic svg { width: 100%; height: 100%; display: block; }

.cl-dias { display: flex; flex-direction: column; }
.cl-dia {
  display: flex; align-items: center; gap: 10px;
  padding: 11px 2px; border-bottom: 1px solid var(--pin-line);
}
.cl-dia:last-child { border-bottom: 0; }
.cl-dia .cl-d {
  flex: 1 1 auto; min-width: 0; font-size: 14.5px; font-weight: 600;
  text-transform: capitalize;
}
.cl-dia .cl-p { width: 40px; text-align: right; font-size: 12px; color: var(--pin-info); font-weight: 600; }
.cl-dia .cl-ic { width: 34px; height: 34px; flex: 0 0 auto; }
/* Máxima y mínima alineadas en columna: así se comparan entre días de un vistazo. */
.cl-dia .cl-max { width: 34px; text-align: right; font-weight: 700; font-size: 15px; }
.cl-dia .cl-min { width: 34px; text-align: right; color: var(--pin-text-muted); font-size: 15px; }

/* ── Bitácora de fiscalización ─────────────────────────────────────────────
   🔴 Las observaciones se distinguen a la vista de un control normal. Un
      control válido más es una línea de histórico; «la credencial es válida
      pero la persona no es la de la foto» es el art. 32 inc. d —mil a mil
      quinientos módulos con secuestro de la mercadería— y es lo único de esta
      pantalla sobre lo que hay que hacer algo. */
.control-asiento {
  padding: 13px 14px; margin-bottom: 10px;
  border: 1px solid var(--pin-line); border-radius: var(--pin-r-sm);
  background: var(--pin-surface);
}
.control-asiento.observado {
  border-color: var(--pin-danger);
  border-left-width: 4px;
  background: rgba(190, 18, 60, .04);
}
.ca-top { display: flex; align-items: baseline; gap: 10px; }
.ca-top b { flex: 1 1 auto; min-width: 0; font-size: 15px; }
.ca-cuando { font-size: 12px; color: var(--pin-text-faint); white-space: nowrap; }
.ca-obs {
  margin: 6px 0 0; font-size: 13.5px; line-height: 1.45;
  color: var(--pin-danger); font-weight: 600;
}
.ca-motivo { margin: 5px 0 0; font-size: 13.5px; color: var(--pin-text-muted); }
.ca-pie {
  display: flex; flex-wrap: wrap; gap: 4px 12px; margin: 9px 0 0;
  font-size: 11.5px; color: var(--pin-text-faint);
}
.ca-pie a { color: var(--pin-accent); font-weight: 600; }


/* ════════════════════════════════════════════════════════════════════════════
   CONFIRMACIÓN — el diálogo propio de la aplicación

   🔴 Reemplaza a `window.confirm()`, que adentro del APK **falla en silencio**:
      `BridgeWebChromeClient.onJsConfirm` de Capacitor devuelve sin resolver el
      resultado si la Activity está terminando, y entonces no pasa nada. Ver
      `app.js § confirmar`.
   ════════════════════════════════════════════════════════════════════════════ */
.confirmar {
  position: fixed; inset: 0; z-index: 130;
  display: flex; align-items: center; justify-content: center;
  padding: 24px;
  background: rgba(16, 24, 20, .55);
  animation: conf-velo .16s var(--pin-ease);
}
.confirmar-caja {
  background: var(--pin-surface); border-radius: var(--pin-r-lg);
  box-shadow: var(--pin-shadow-pop);
  padding: 24px; width: 100%; max-width: 360px;
  animation: conf-caja .2s var(--pin-ease);
}
.confirmar-caja b { display: block; font-size: 17px; margin-bottom: 6px; }
.confirmar-caja p {
  margin: 0; font-size: 14px; line-height: 1.55; color: var(--pin-text-muted);
}
/* Los botones van uno al lado del otro y el seguro primero: en un teléfono, el
   pulgar cae naturalmente a la derecha, así que la acción destructiva no puede
   ser la que queda debajo del dedo por descuido. */
.confirmar-botones {
  display: flex; gap: 10px; margin-top: 20px;
}
.confirmar-botones .btn { margin: 0; flex: 1; }

@keyframes conf-velo { from { opacity: 0; } }
@keyframes conf-caja { from { opacity: 0; transform: translateY(10px) scale(.97); } }

@media (prefers-reduced-motion: reduce) {
  .confirmar, .confirmar-caja { animation: none; }
}


/* ════════════════════════════════════════════════════════════════════════════
   EL SISTEMA DE LA PANTALLA DE INICIO

   🔴 La pantalla no se sentía vacía por falta de adorno: se sentía vacía porque
      no tenía información encima. El inicio del inspector mostraba el clima, su
      nombre y una línea de ayuda, y debajo quedaban casi trescientos píxeles de
      nada — mientras el sistema ya sabía cuántos puestos están abiertos, cuáles
      reportan desde la web y cuáles dejaron de reportar hace rato.

      Llenar ese hueco con ilustraciones lo taparía. Llenarlo con lo que la
      persona necesita para trabajar lo resuelve y de paso lo llena.

   ⚠️ Todas las cifras salen de datos que ESE ROL ya puede leer. No hay ningún
      número que dependa de una consulta que las reglas no permitan: un tablero
      con un dato inventado es peor que un tablero vacío.
   ════════════════════════════════════════════════════════════════════════════ */

/* ── La escala de ritmo ─────────────────────────────────────────────────────
   🔴 Los espacios eran valores elegidos de a uno: 2, 10, 12, 14, 16, 18, 20, 28.
      Nada estaba mal y nada rimaba, y esa desprolijidad difusa es la otra mitad
      de la sensación de vacío. Seis pasos y una regla: SEPARA MÁS LO QUE MENOS
      SE RELACIONA. */
:root {
  --e1: 4px;    /* dentro de una línea          */
  --e2: 8px;    /* dentro de una tarjeta        */
  --e3: 12px;   /* entre tarjetas hermanas      */
  --e4: 20px;   /* antes de un título           */
  --e5: 28px;   /* entre secciones              */
  --e6: 44px;   /* respiro final                */
}

/* ── Un dato tiene tres partes, no una ──────────────────────────────────────
   🔴 «Viento 13 km/h E» se leía igual que una oración. Un dato es VALOR +
      UNIDAD + RÓTULO, con tres jerarquías distintas: monoespaciada tabular para
      el valor, unidad más chica y apagada, rótulo en versalitas.

   ⚠️ `tabular-nums` no es un detalle: sin eso un 1 es más angosto que un 8 y las
      cifras bailan al refrescarse. Se nota justo en una pantalla que se
      actualiza sola. */
.dato { display: flex; flex-direction: column; gap: var(--e1); min-width: 0; }
.dato .v {
  font-family: var(--pin-font-mono);
  font-size: 22px; font-weight: 500; line-height: 1;
  letter-spacing: -.03em; font-variant-numeric: tabular-nums;
  color: var(--pin-text);
}
.dato .v u { text-decoration: none; font-size: 12px; color: var(--pin-text-muted); margin-left: 2px; }
.dato .r {
  font-family: var(--pin-font-mono);
  font-size: 9.5px; letter-spacing: .1em; text-transform: uppercase;
  color: var(--pin-text-faint); line-height: 1.25;
}

/* ── El tablero de tres cifras ────────────────────────────────────────────── */
.tablero { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--e2); }
.cifra {
  background: var(--pin-bg-suave); border: 1px solid var(--pin-line);
  border-radius: var(--pin-r-md); padding: 11px 10px; min-width: 0;
}
/* 🔴 El acento se gasta en UN solo lugar: la cifra accionable. Si todas gritan,
   ninguna se lee. «Reportan por web» es la única sobre la que el inspector
   puede hacer algo hoy — decirle a esa gente que instale la aplicación. */
.cifra.marcada { border-color: var(--pin-warn); background: color-mix(in srgb, var(--pin-warn) 9%, var(--pin-bg-suave)); }
.cifra.marcada .v { color: var(--pin-warn); }

/* ── Estados: un solo idioma visual ─────────────────────────────────────────
   🔴 Un permiso puede estar vigente, suspendido, caduco, revocado o vacante, y
      la credencial además vencida. Eran seis frases distintas en cada pantalla.
      Ahora son seis formas: color, punto y palabra — idénticas en el resultado
      del escaneo, en el globo del mapa y en la lista del panel. El inspector
      las aprende una vez.

   ⚠️ El ámbar dice «esto se destraba solo el 30 de septiembre»; el rojo dice
      «esto no se destraba». La diferencia importa: el art. 31 inc. c define la
      suspensión como TEMPORAL, y mostrarla igual que una revocación las vuelve
      indistinguibles. */
.chip-estado {
  display: inline-flex; align-items: center; gap: var(--e2);
  padding: 5px 12px 5px 9px; border-radius: var(--pin-r-pill);
  font-size: 12.5px; font-weight: 600; letter-spacing: .01em;
  border: 1px solid var(--pin-line); background: var(--pin-surface);
  color: var(--pin-text-muted);
}
.chip-estado i {
  width: 8px; height: 8px; border-radius: 50%; flex: 0 0 auto;
  background: var(--pin-text-faint);
}
.chip-estado.ok   { color: var(--pin-accent); border-color: color-mix(in srgb, var(--pin-accent) 32%, transparent); background: var(--pin-accent-dim); }
.chip-estado.ok i { background: var(--pin-accent); }
.chip-estado.espera   { color: #8A6008; border-color: color-mix(in srgb, var(--pin-warn) 42%, transparent); background: color-mix(in srgb, var(--pin-warn) 13%, var(--pin-surface)); }
.chip-estado.espera i { background: var(--pin-warn); }
.chip-estado.mal   { color: var(--pin-danger); border-color: color-mix(in srgb, var(--pin-danger) 32%, transparent); background: color-mix(in srgb, var(--pin-danger) 9%, var(--pin-surface)); }
.chip-estado.mal i { background: var(--pin-danger); }

/* ── Lo que requiere atención ───────────────────────────────────────────────
   La franja de la izquierda codifica la urgencia sin agregar una palabra: se
   lee de reojo, que es como se mira una lista mientras se camina. */
.atencion { display: flex; flex-direction: column; gap: var(--e2); }
.at-item {
  display: flex; align-items: center; gap: 10px;
  border: 1px solid var(--pin-line); border-left-width: 3px;
  border-radius: var(--pin-r-sm); padding: 10px 12px;
  background: var(--pin-surface); min-width: 0;
}
.at-item.ok   { border-left-color: var(--pin-accent); }
.at-item.tibio { border-left-color: var(--pin-warn); }
.at-item.frio { border-left-color: var(--pin-danger); }
.at-item span { min-width: 0; flex: 1; }
.at-item b { display: block; font-size: 13.5px; font-weight: 600; line-height: 1.25; }
.at-item small { display: block; font-size: 11.5px; color: var(--pin-text-muted); }
.at-item .der {
  font-family: var(--pin-font-mono); font-size: 11px;
  color: var(--pin-text-muted); flex: 0 0 auto; font-variant-numeric: tabular-nums;
}

/* ── Títulos de sección de la pantalla de inicio ───────────────────────────── */
.seccion-tit {
  font-size: 11px; font-weight: 700; letter-spacing: .09em; text-transform: uppercase;
  color: var(--pin-text-muted); margin: var(--e4) 0 var(--e2);
}
.seccion-tit:first-child { margin-top: 0; }

/* ── El piso de la pantalla ─────────────────────────────────────────────────
   🔴 El contenido se cortaba y la barra quedaba flotando sobre la nada. Un
      cierre discreto le da fondo a la pantalla y le dice a la persona que llegó
      al final, en vez de dejarla dudando si falta algo por cargar. */
.piso {
  margin-top: var(--e6); padding-top: var(--e3);
  border-top: 1px solid var(--pin-line);
  font-family: var(--pin-font-mono);
  font-size: 10.5px; letter-spacing: .08em; text-transform: uppercase;
  color: var(--pin-text-faint); text-align: center;
}

/* ── El clima como instrumento ──────────────────────────────────────────────
   🔴 La tarjeta ocupaba una quinta parte de la pantalla y la mayor parte era
      verde liso. Y el criterio del proyecto ya decía lo correcto: para quien
      trabaja parado en la arena, EL VIENTO ES EL DATO DEL DÍA — 24° o 26° no
      cambian nada, 38 km/h decide si se abre el carro.

   🔴 La rosa se dibuja y no se escribe. «13 km/h E» obliga a traducir una letra
      a una dirección; la aguja se lee sin traducir, y el vendedor ve de un
      vistazo si el viento le va a dar de frente al carro. */
.clima-inst {
  background: var(--pin-accent); color: #fff;
  border-radius: var(--pin-r-lg); padding: 15px 16px;
}
.clima-inst .fila { display: flex; align-items: center; gap: 14px; }
.clima-inst .rosa { width: 74px; height: 74px; flex: 0 0 auto; }
.clima-inst .cl-dibujo { width: 74px; height: 74px; flex: 0 0 auto; }

/* ── Las tres caras ────────────────────────────────────────────────────────
   🔴 SE APILAN CON GRID, no con `position:absolute`. Con absolute habría que
      fijarle un alto a la pista —y ese alto sería el de UNA cara, así que la
      más alta se cortaría en los teléfonos angostos—. Poniendo las tres en la
      MISMA celda de grid (`grid-area: 1/1`), el contenedor mide lo que la más
      alta y ninguna se recorta, sin un solo número escrito a mano.

   ⚠️ La que no está visible además queda sin recibir toques: si no, el botón de
      una cara invisible seguiría capturando el dedo. */
.clima-inst .caras { display: grid; }
.clima-inst .cara {
  grid-area: 1 / 1;
  opacity: 0; transform: translateY(7px) scale(.985);
  transition: opacity .55s var(--pin-ease), transform .55s var(--pin-ease);
  pointer-events: none;
}
.clima-inst .cara.activa { opacity: 1; transform: none; pointer-events: auto; }

/* Con `prefers-reduced-motion` no rota: se apilan y se ven las tres. Apagar la
   rotación dejando una sola escondería dos tercios de la información. */
.clima-inst .caras.quietas { display: block; }
.clima-inst .caras.quietas .cara { opacity: 1; transform: none; pointer-events: auto; }
.clima-inst .caras.quietas .cara + .cara {
  margin-top: 12px; padding-top: 12px; border-top: 1px solid rgba(255,255,255,.18);
}

/* Los puntos dicen que hay más de una cara. Sin ellos, una tarjeta que cambia
   sola se lee como que parpadea o se rompió. */
.clima-inst .puntos { flex: 1; display: flex; gap: 5px; justify-content: center; align-self: center; }
.clima-inst .puntos i {
  width: 5px; height: 5px; border-radius: 50%; background: rgba(255,255,255,.38);
  transition: background .45s var(--pin-ease), transform .45s var(--pin-ease);
}
.clima-inst .puntos i.on { background: #fff; transform: scale(1.3); }

.clima-inst .grande,
.clima-inst .viento {
  font-family: var(--pin-font-mono); font-size: 30px; font-weight: 500;
  line-height: 1; letter-spacing: -.03em; font-variant-numeric: tabular-nums;
}
.clima-inst .grande u,
.clima-inst .viento u { text-decoration: none; font-size: 13px; opacity: .82; margin-left: 3px; }
.clima-inst .rumbo {
  font-family: var(--pin-font-mono); font-size: 9.5px; letter-spacing: .11em;
  text-transform: uppercase; opacity: .84; margin-top: 6px;
}
.clima-inst .mar { font-size: 11.5px; opacity: .95; margin-top: 9px; line-height: 1.4; }
.clima-inst .pie {
  display: flex; gap: 0; align-items: flex-end;
  margin-top: 14px; padding-top: 12px; border-top: 1px solid rgba(255,255,255,.22);
}
.clima-inst .pie .dato { flex: 1; }
.clima-inst .pie .dato .v { color: #fff; font-size: 18px; }
.clima-inst .pie .dato .r { color: rgba(255,255,255,.78); }
.clima-inst .pie .ver {
  flex: 0 0 auto; background: none; border: 0; color: #fff; cursor: pointer;
  font: inherit; font-size: 11.5px; padding: 4px 0 4px 8px;
  display: inline-flex; align-items: center; gap: 3px;
}
.clima-inst .pie .ver svg { width: 14px; height: 14px; }
.clima-inst .pie .ver:active { opacity: .7; }

@media (max-width: 340px) {
  .clima-inst .rosa { width: 58px; height: 58px; }
  .clima-inst .grande,
  .clima-inst .viento { font-size: 25px; }
  .clima-inst .cl-dibujo { width: 58px; height: 58px; }
}
