/* ============================================================
   BAJAJ RIDER DISTRICT — sistema visual
   Base: premium-ui-system (tokens, motion, a11y).

   La paleta NO se invento: se muestreo pixel a pixel del arte del cliente.
     neon    #0167FD  <- bordes con glow, "DISTRICT", iconos de campo
     profundo#0023A2  <- salpicaduras del fondo, relleno de botones
     negro   #00030A  <- base de las pantallas (negro con tinte azul, no gris)
     blanco  #FEFEFE  <- titulares y texto

   El lenguaje de formas del arte es CHAFLAN + BORDE NEON: cada caja tiene las cuatro
   esquinas cortadas en diagonal y un contorno azul que brilla hacia afuera. Aqui se
   reproduce con clip-path (la forma) + drop-shadow (el brillo). No con border-radius:
   una pildora redondeada seria de otra marca.

   Contrastes verificados (WCAG 2.2) sobre el negro base #00030A:
     blanco #FEFEFE ......... 19.9:1
     texto  #E8EDF7 ......... 16.8:1
     apoyo  #93A9CE .......... 7.6:1
     neon   #0167FD .......... 3.4:1  -> SOLO para bordes, iconos y texto grande (>=24px),
                                         nunca para texto corrido. Los textos azules del
                                         arte usan #5B9BFF (6.1:1).
   ============================================================ */

@font-face {
  font-family: "Archivo";
  font-style: normal;
  font-weight: 400 800;
  font-stretch: 62% 125%;
  font-display: swap;
  src: url("/fonts/archivo-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304,
    U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Archivo";
  font-style: normal;
  font-weight: 400 800;
  font-stretch: 62% 125%;
  font-display: swap;
  src: url("/fonts/archivo-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308,
    U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113,
    U+2C60-2C7F, U+A720-A7FF;
}

:root {
  color-scheme: dark;

  /* ---------- TIER 1: primitivos (muestreados del arte) ---------- */
  --neon: #0167fd;
  --profundo: #0023a2;
  --negro: #00030a;
  --blanco: #fefefe;

  --azul-texto: #5b9bff; /* el azul del arte, subido de luminancia hasta ser legible */
  --azul-tenue: #93a9ce;
  --superficie: #04070f;
  --superficie-alta: #060b16;
  --rojo: #ff5a67;
  --verde: #2fd07a;

  /* Espaciado — base 4px, ritmo 8px */
  --space-1: 4px;   --space-2: 8px;   --space-3: 12px;  --space-4: 16px;
  --space-5: 24px;  --space-6: 32px;  --space-7: 48px;  --space-8: 64px;

  /* Tipografia fluida (360 -> 480, que es el rango real de un celular) */
  --text--1: clamp(0.8125rem, 0.77rem + 0.19vw, 0.875rem);
  --text-0:  clamp(0.9375rem, 0.87rem + 0.31vw, 1.0625rem);
  --text-1:  clamp(1.0625rem, 0.96rem + 0.47vw, 1.25rem);
  --text-2:  clamp(1.25rem, 1.08rem + 0.78vw, 1.5625rem);
  --text-3:  clamp(1.75rem, 1.4rem + 1.56vw, 2.375rem);
  --text-4:  clamp(2.5rem, 1.9rem + 2.7vw, 3.5rem);

  --font-body: "Archivo", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
  --font-mono: ui-monospace, "SF Mono", "Cascadia Mono", "Segoe UI Mono", Menlo, monospace;

  /* Chaflan: el corte de esquina del arte. Un solo sistema, dos tamaños. */
  --corte: 11px;
  --corte-grande: 17px;

  /* Motion — valores de casa, no se inventan otros */
  --dur-instant: 120ms;
  --dur-toggle: 240ms;
  --dur-modal: 400ms;
  --dur-page: 500ms;
  --ease-out: cubic-bezier(0.33, 1, 0.68, 1);
  --ease-out-strong: cubic-bezier(0.23, 1, 0.32, 1);
  --ease-in: cubic-bezier(0.32, 0, 0.67, 0);
  --ease-in-out: cubic-bezier(0.45, 0, 0.55, 1);

  /* ---------- TIER 2: semanticos ---------- */
  --texto: #e8edf7;
  --texto-fuerte: var(--blanco);
  --texto-apoyo: var(--azul-tenue);
  --borde: color-mix(in srgb, var(--neon) 55%, transparent);

  /* El brillo del neon. Dos capas: una cerrada que define el filo y otra abierta que
     tiñe el aire alrededor, igual que en el arte. */
  --glow: drop-shadow(0 0 3px color-mix(in srgb, var(--neon) 70%, transparent))
          drop-shadow(0 0 14px color-mix(in srgb, var(--neon) 45%, transparent));
  --glow-fuerte: drop-shadow(0 0 4px color-mix(in srgb, var(--neon) 85%, transparent))
                 drop-shadow(0 0 20px color-mix(in srgb, var(--neon) 60%, transparent));
}

/* ---------------------------------------------------------------- reset */

*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; }

html { -webkit-text-size-adjust: 100%; }

body {
  min-height: 100svh;
  background: var(--negro);
  color: var(--texto);
  font-family: var(--font-body);
  font-size: var(--text-0);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  /* La experiencia es una pila de pantallas, no un documento: no debe hacer scroll de
     rebote ni tirar hacia abajo para refrescar en mitad de un formulario. */
  overscroll-behavior: none;
}

/* El atributo hidden tiene que ganar SIEMPRE. Sin esto, cualquier clase con `display`
   (.forma, .escaner, .ficha, todas flex) lo anula y el bloque se queda visible aunque el JS
   lo haya ocultado: el formulario de recuperar seguia en pantalla junto al mensaje de
   exito, y en el Scanner 1 se veian a la vez la clave, el lector y la ficha. */
[hidden] { display: none !important; }

img, video, canvas { display: block; max-width: 100%; }
button, input, select { font: inherit; color: inherit; }
button { background: none; border: 0; padding: 0; cursor: pointer; }
:focus-visible { outline: 2px solid var(--neon); outline-offset: 3px; border-radius: 2px; }

.oculto-visual {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap;
}
/* Fuera del flujo y fuera del foco, pero enviable: es la trampa de bots y el input file. */
.trampa { position: absolute; left: -9999px; width: 1px; height: 1px; opacity: 0; pointer-events: none; }

.noscript {
  position: fixed; inset: auto 0 0; padding: var(--space-4);
  background: var(--profundo); color: var(--blanco); text-align: center; font-size: var(--text--1);
}

/* ---------------------------------------------------------------- marco 9:16 */

.marco {
  /* El arte es 1080x1920 exacto. En un monitor o una tablet no se estira: se encierra a
     su proporcion y el resto queda negro, como una vertical de verdad. */
  width: min(100vw, calc(100svh * 9 / 16));
  height: 100svh;
  margin-inline: auto;
  position: relative;
  overflow: hidden;
  background: var(--negro);
}

.escenario { position: absolute; inset: 0; }

/* ---------------------------------------------------------------- pantallas */

.pantalla {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  padding: max(var(--space-5), env(safe-area-inset-top)) var(--space-5)
           max(var(--space-5), env(safe-area-inset-bottom));
  /* Cada pantalla es su propio ambito de scroll. El titular no se va: solo el contenido. */
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;

  /* Fondo de marca: la textura del cliente bajo un velo que la vuelve legible. Sin el
     velo, el azul saturado del arte deja los textos ilegibles; con el, se lee como los
     mockups del cliente, donde la salpicadura solo asoma en los bordes. */
  background-color: var(--negro);
  background-image:
    radial-gradient(120% 70% at 50% 0%, rgba(0, 3, 10, 0.55) 0%, rgba(0, 3, 10, 0.86) 55%, var(--negro) 100%),
    url("/brand/fondo-1080.webp");
  background-size: auto, cover;
  background-position: center, center top;
  background-repeat: no-repeat;

  opacity: 0;
  transform: translate3d(22px, 0, 0);
  pointer-events: none;
  transition: opacity var(--dur-toggle) var(--ease-out),
              transform var(--dur-page) var(--ease-out-strong);
}
.pantalla[hidden] { display: none; }
.pantalla.is-activa { opacity: 1; transform: none; pointer-events: auto; }
.pantalla.is-saliendo {
  opacity: 0;
  transform: translate3d(-14px, 0, 0);
  transition-timing-function: var(--ease-in);
}

/* Entrada escalonada de los bloques de cada pantalla. Solo transform y opacity. */
.pantalla.is-activa > * {
  animation: sube var(--dur-modal) var(--ease-out-strong) both;
}
.pantalla.is-activa > *:nth-child(1) { animation-delay: 40ms; }
.pantalla.is-activa > *:nth-child(2) { animation-delay: 90ms; }
.pantalla.is-activa > *:nth-child(3) { animation-delay: 130ms; }
.pantalla.is-activa > *:nth-child(4) { animation-delay: 170ms; }
.pantalla.is-activa > *:nth-child(5) { animation-delay: 210ms; }
.pantalla.is-activa > *:nth-child(6) { animation-delay: 240ms; }

@keyframes sube {
  from { opacity: 0; transform: translate3d(0, 14px, 0); }
  to   { opacity: 1; transform: none; }
}

/* ---------------------------------------------------------------- 1. inicio */

.pantalla--inicio {
  padding: 0;
  background-image: none; /* el arte de inicio ya trae su propio fondo completo */
  justify-content: flex-end;
  overflow: hidden;
}

/* El arte cubre TODA la pantalla y el pie va superpuesto encima. Antes se usaba contain
   para no recortar nada, pero dejaba franjas negras arriba y abajo y la portada no se veia
   completa. Con cover se recorta un 9% por lado en un celular 9:19.5, y ahi el titular
   "RIDER DISTRICT" (que llega casi al borde del lienzo) todavia entra: es el elemento que
   marca cuanto recorte se puede permitir. */
.inicio__arte {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center center;
}

.inicio__pie {
  position: relative; /* por encima del arte */
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-8) var(--space-5) max(var(--space-5), env(safe-area-inset-bottom));
  /* Velo de abajo hacia arriba: el arte termina en una zona con brillo azul y sin esto el
     enlace de recuperar se pierde sobre el. */
  background: linear-gradient(180deg, rgba(0, 3, 10, 0) 0%, rgba(0, 3, 10, 0.72) 45%, rgba(0, 3, 10, 0.94) 100%);
}

/* El boton del cliente, tal cual. Es un <button> de verdad y no un <img> con click: asi
   tiene foco visible, respuesta tactil y lo anuncia un lector de pantalla. */
/* El boton del cliente, tal cual, pero contenido: al 88% del ancho competia con el titular
   del arte y quedaba a 19 px de el. Al 74% baja a ~72 px de alto, deja unos 32 px de aire
   bajo "DISTRICT" y se lee como un boton, no como otra franja del cartel. */
.boton-arte {
  width: min(66%, 268px);
  filter: var(--glow);
  transition: transform var(--dur-instant) var(--ease-out), filter var(--dur-toggle) var(--ease-out);
}
.boton-arte:active { transform: scale(0.975); filter: var(--glow-fuerte); }
.boton-arte img { width: 100%; height: auto; }

.enlace {
  color: var(--azul-texto);
  font-size: var(--text--1);
  font-weight: 600;
  letter-spacing: 0.02em;
  padding: var(--space-2) var(--space-3);
  text-decoration: underline;
  text-underline-offset: 4px;
  text-decoration-thickness: 1px;
  transition: color var(--dur-instant) var(--ease-out);
}
.enlace:active { color: var(--blanco); }

/* ---------------------------------------------------------------- retroceso */

/* Va absoluto y no en el flujo: asi el titular sigue centrado en la pantalla y no se
   descoloca por tener un boton a un lado. */
.volver {
  position: absolute;
  top: max(var(--space-4), env(safe-area-inset-top));
  left: var(--space-3);
  z-index: 5;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 46px;
  height: 46px;
  color: var(--texto-apoyo);
  transition: color var(--dur-instant) var(--ease-out), transform var(--dur-instant) var(--ease-out);
}
.volver:active { color: var(--blanco); transform: translateX(-2px); }

/* Cuando el titular va justo detras del boton (pantallas sin logo, como FOTOGRAFÍA) hay
   que bajarlo o la flecha queda pegada a la primera letra. */
.volver + .titular { margin-top: var(--space-6); }

/* El Scanner no tiene ni logo ni boton de volver: sin esto el titular nace pegado al borde. */
[data-pantalla="scanner"] .titular { margin-top: var(--space-5); }

/* ---------------------------------------------------------------- marca y titulares */

.marca {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2);
  margin-bottom: var(--space-4);
}
.marca__logo { width: 132px; height: auto; }

/* Lockup "RIDER DISTRICT": blanco + azul en caja, como en el arte. */
.marca__evento {
  display: flex;
  align-items: stretch;
  gap: 5px;
  font-size: var(--text--1);
  font-weight: 800;
  font-stretch: 78%;
  letter-spacing: 0.05em;
  transform: skewX(-9deg);
}
.marca__evento span { color: var(--blanco); }
.marca__evento em {
  font-style: normal;
  color: var(--blanco);
  background: var(--neon);
  padding: 0 6px;
  filter: var(--glow);
}

/* Titular grunge del arte: condensado, muy pesado, inclinado y en mayusculas.
   En los mockups del cliente el titulo ocupa ~86% del ancho de la pantalla, asi que va
   dimensionado en vw y muy condensado (62% es el minimo de la Archivo variable). Con un
   tamaño en rem se quedaba en la mitad de ancho y la pantalla perdia toda su fuerza. */
.titular {
  font-size: clamp(3rem, var(--fs, 15.5vw), 5.5rem);
  font-weight: 800;
  font-stretch: 62%;
  line-height: 0.92;
  letter-spacing: 0.005em;
  text-align: center;
  color: var(--texto-fuerte);
  transform: skewX(-9deg);
  text-shadow: 0 0 24px rgba(1, 103, 253, 0.35);
  /* Los titulos largos ("FOTOGRAFÍA", "RECUPERAR") no deben partirse en dos lineas ni
     desbordar el marco: se encogen solos si hace falta. */
  white-space: nowrap;
  max-width: 100%;
}

/* Titulo como imagen del cliente. El arte ya trae su halo, asi que no lleva text-shadow ni
   inclinacion: viene aplicada en el propio archivo. El ancho es el mismo 85% que usan los
   titulos de texto, para que todas las pantallas mantengan la misma medida. */
.titular--arte {
  display: block;
  width: 100%;
  transform: none;
  text-shadow: none;
  white-space: normal;
  line-height: 0;
}
.titular--arte img {
  display: block;
  width: min(100%, 460px);
  height: auto;
  margin-inline: auto;
}

/* Cada titulo lleva su tamaño calibrado para que TODOS ocupen ~85% del ancho, como en los
   mockups. Con un solo tamaño para los cuatro, "REGISTRO" (8 letras) llenaba el 66% y
   "FOTOGRAFÍA" (10) el 84%, y las pantallas no parecian de la misma familia. Son cuatro
   titulos fijos, asi que se calibran a mano en vez de medirlos con JS en cada carga. */
[data-pantalla="registro"]  .titular { --fs: 19.1vw; }
[data-pantalla="foto"]      .titular { --fs: 15.7vw; }
/* "QR PERSONAL" va en UNA linea, asi que necesita bastante menos cuerpo que el resto para
   ocupar el mismo 85%: en dos lineas cabia a 18.3vw, en una se desbordaba al 109%. */
[data-pantalla="qr"]        .titular { --fs: 14.3vw; }
[data-pantalla="recuperar"] .titular { --fs: 16.3vw; }

/* Variante de dos palabras: la segunda en azul. Va en UNA sola linea. */
.titular--doble {
  display: flex;
  flex-direction: row;
  align-items: baseline;
  justify-content: center;
  gap: 0.28em;
  white-space: nowrap;
}
.titular--doble em {
  font-style: normal;
  color: var(--neon);
  text-shadow: 0 0 18px rgba(1, 103, 253, 0.75);
}

.sub {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  margin-top: var(--space-3);
  margin-bottom: var(--space-5);
  font-size: var(--text--1);
  font-weight: 600;
  color: var(--texto);
  text-align: center;
}
/* Las barras "//" del arte, dibujadas con CSS en vez de con texto: asi no las lee un
   lector de pantalla ni se rompen al cambiar de fuente. */
.sub__barra {
  flex: 0 0 auto;
  width: 14px;
  height: 12px;
  background: var(--neon);
  clip-path: polygon(38% 0, 100% 0, 62% 100%, 0 100%);
  filter: var(--glow);
}
.sub--fuerte {
  font-size: var(--text-1);
  font-weight: 800;
  font-stretch: 84%;
  letter-spacing: 0.04em;
  color: var(--texto-fuerte);
}
/* Sin las barras "//" del arte: es una nota informativa, no un rotulo. */
.sub--simple {
  margin-top: var(--space-2);
  margin-bottom: var(--space-4);
  font-weight: 600;
  color: var(--texto-apoyo);
}
/* En la pantalla de la foto la leyenda explica para que sirve el retrato: se lee antes de
   decidir, asi que va con mas cuerpo y en el color de texto normal. */
.sub--grande {
  font-size: var(--text-0);
  color: var(--texto);
  line-height: 1.45;
  max-width: 30ch;
  margin-inline: auto;
}

/* ---------------------------------------------------------------- campos */

/* El formulario ocupa el alto sobrante y se reparte: sin esto quedaba un tercio de
   pantalla negro debajo del boton y la composicion se veia caida hacia arriba. */
.forma {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  flex: 1 1 auto;
  justify-content: center;
  padding-bottom: var(--space-4);
}
.forma > .boton--primario { margin-top: var(--space-3); }

/* .campo es solo el grupo: marco + mensaje de error.
   El mensaje tiene que quedar FUERA del marco, no dentro: el clip-path recorta todo su
   subarbol y el fondo del borde lo tiñe, asi que un error dentro del marco se veia como
   una barra roja sin texto legible. */
.campo { display: flex; flex-direction: column; }

/* Dos capas para dibujar el borde neon con esquinas cortadas: .campo__marco es la capa de
   color (hace de trazo) y .campo__caja la interior (el relleno). Las dos llevan el mismo
   clip-path y los 2px de diferencia son el grosor.
   No se puede hacer con border + border-radius porque el arte no tiene esquinas redondas,
   y un box-shadow quedaria recortado por el clip-path: de ahi el drop-shadow. */
.campo__marco {
  --c: var(--corte);
  background: var(--borde);
  padding: 2px;
  clip-path: polygon(
    var(--c) 0, 100% 0, 100% calc(100% - var(--c)),
    calc(100% - var(--c)) 100%, 0 100%, 0 var(--c)
  );
  filter: var(--glow);
  transition: filter var(--dur-toggle) var(--ease-out), background var(--dur-toggle) var(--ease-out);
}
.campo__marco:focus-within { background: var(--neon); filter: var(--glow-fuerte); }
.campo.is-mal .campo__marco { background: var(--rojo); filter: drop-shadow(0 0 10px rgba(255, 90, 103, 0.5)); }

.campo__caja {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: 0 var(--space-4);
  min-height: 58px; /* objetivo tactil holgado; el minimo accesible es 44 */
  background: var(--superficie);
  clip-path: polygon(
    var(--c) 0, 100% 0, 100% calc(100% - var(--c)),
    calc(100% - var(--c)) 100%, 0 100%, 0 var(--c)
  );
  position: relative;
}

.campo__icono { flex: 0 0 auto; color: var(--neon); display: flex; }
.campo.is-mal .campo__icono { color: var(--rojo); }

.campo input,
.campo select {
  flex: 1 1 auto;
  min-width: 0;
  background: none;
  border: 0;
  padding: var(--space-4) 0;
  font-size: var(--text-1);
  font-weight: 700;
  color: var(--texto-fuerte);
  /* 16px reales como minimo: por debajo de eso, Safari en iOS hace zoom al enfocar el
     campo y descoloca toda la pantalla. --text-1 ya arranca en 17px. */
  appearance: none;
}
.campo input::placeholder { color: var(--texto-apoyo); font-weight: 600; opacity: 1; }
.campo select { cursor: pointer; }
.campo select:invalid { color: var(--texto-apoyo); font-weight: 600; }
.campo select option { background: var(--superficie); color: var(--texto-fuerte); }
/* La flecha se dibuja aparte porque appearance:none quita la nativa. */
.campo__flecha { flex: 0 0 auto; color: var(--neon); pointer-events: none; display: flex; }

.campo__error {
  font-size: var(--text--1);
  font-weight: 600;
  color: var(--rojo);
  padding: 6px var(--space-2) 0;
  min-height: 0;
}
.campo__error:empty { display: none; }

.forma__error,
.aviso {
  font-size: var(--text--1);
  font-weight: 600;
  color: var(--rojo);
  text-align: center;
  padding: var(--space-2);
}
.forma__error:empty { display: none; }
.aviso--suave { color: var(--texto-apoyo); }

.nota {
  font-size: var(--text--1);
  color: var(--texto-apoyo);
  text-align: center;
  padding: 0 var(--space-2);
  line-height: 1.5;
}

/* ---------------------------------------------------------------- botones */

.acciones { display: flex; flex-direction: column; gap: var(--space-3); margin-top: auto; padding-top: var(--space-5); }

/* ---- las dos opciones de la foto: cuadrados gemelos ---- */

.elegir {
  display: grid;
  grid-template-columns: 1fr 1fr; /* mismas columnas: ninguna via pesa mas que la otra */
  gap: var(--space-3);
  margin-top: var(--space-2);     /* pegadas a la leyenda, no flotando en medio */
}
.elegir[hidden] { display: none !important; }

.cuadro {
  --c: var(--corte);
  position: relative;
  aspect-ratio: 1 / 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-3);
  background: var(--borde);
  padding: 2px;
  clip-path: polygon(
    var(--c) 0, calc(100% - var(--c)) 0, 100% var(--c), 100% calc(100% - var(--c)),
    calc(100% - var(--c)) 100%, var(--c) 100%, 0 calc(100% - var(--c)), 0 var(--c)
  );
  filter: var(--glow);
  transition: transform var(--dur-instant) var(--ease-out), filter var(--dur-toggle) var(--ease-out);
}
/* El relleno en un pseudo-elemento, como en .boton--linea: el clip-path recorta a los
   hijos, asi que el fondo no puede ir en un div interior sin duplicar la forma. */
.cuadro::before {
  content: "";
  position: absolute;
  inset: 2px;
  background: var(--superficie-alta);
  clip-path: inherit;
  z-index: -1;
}
.cuadro:active { transform: scale(0.975); filter: var(--glow-fuerte); }

.cuadro__ico { color: var(--neon); display: flex; }
.cuadro__txt {
  font-size: var(--text-0);
  font-weight: 800;
  font-stretch: 80%;
  letter-spacing: 0.06em;
  line-height: 1.15;
  color: var(--blanco);
  text-align: center;
  transform: skewX(-7deg);
}

.boton {
  --c: var(--corte-grande);
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-3);
  min-height: 62px;
  padding: var(--space-3) var(--space-5);
  clip-path: polygon(
    var(--c) 0, calc(100% - var(--c)) 0, 100% var(--c), 100% calc(100% - var(--c)),
    calc(100% - var(--c)) 100%, var(--c) 100%, 0 calc(100% - var(--c)), 0 var(--c)
  );
  font-weight: 800;
  font-stretch: 80%;
  font-size: var(--text-1);
  letter-spacing: 0.06em;
  color: var(--blanco);
  transform: skewX(-9deg);
  transition: transform var(--dur-instant) var(--ease-out), filter var(--dur-toggle) var(--ease-out),
              opacity var(--dur-toggle) var(--ease-out);
}
/* El texto se des-inclina para que solo la caja quede en diagonal, como en el arte:
   el chaflan es de la forma, no de la tipografia... salvo el titulo, que si es italico. */
.boton__txt { transform: skewX(9deg) skewX(-6deg); display: inline-block; }
.boton__ico { transform: skewX(9deg); display: flex; flex: 0 0 auto; }

.boton--primario { background: linear-gradient(180deg, var(--neon) 0%, var(--profundo) 100%); filter: var(--glow); }
.boton--linea { background: var(--borde); padding: 2px; filter: var(--glow); }
/* En el boton de linea el relleno lo pone un pseudo-elemento para no meter otro div. */
.boton--linea::before {
  content: "";
  position: absolute;
  inset: 2px;
  background: var(--superficie-alta);
  clip-path: inherit;
  z-index: -1;
}
.boton--linea .boton__ico { color: var(--neon); }
.boton--fantasma {
  background: none;
  min-height: 52px;
  font-size: var(--text-0);
  color: var(--texto-apoyo);
  clip-path: none;
}
.boton--fantasma .boton__ico { color: var(--texto-apoyo); }

.boton:active { transform: skewX(-9deg) scale(0.978); }
.boton[disabled] { opacity: 0.55; pointer-events: none; }
.boton[hidden] { display: none; }

/* Estado de carga: el giro aparece y el texto se va. Sin esto, el doble toque en celular
   crea dos registros (y el segundo choca contra el indice unico con un 409 desconcertante). */
.boton__giro { display: none; transform: skewX(9deg); }
.boton.is-cargando .boton__txt,
.boton.is-cargando .boton__ico { display: none; }
.boton.is-cargando .boton__giro { display: flex; animation: gira 900ms linear infinite; }
@keyframes gira { to { transform: skewX(9deg) rotate(360deg); } }

/* ---------------------------------------------------------------- visor de foto */

.visor { display: flex; justify-content: center; }

.visor__caja {
  --c: var(--corte-grande);
  position: relative;
  width: 100%;
  aspect-ratio: 3 / 4; /* el encuadre del arte, y el que se recorta al capturar */
  background: var(--borde);
  padding: 2px;
  clip-path: polygon(
    var(--c) 0, calc(100% - var(--c)) 0, 100% var(--c), 100% calc(100% - var(--c)),
    calc(100% - var(--c)) 100%, var(--c) 100%, 0 calc(100% - var(--c)), 0 var(--c)
  );
  filter: var(--glow);
  overflow: hidden;
}
.visor__caja > * { clip-path: inherit; }

.visor__vacio,
.visor__video,
.visor__previa {
  position: absolute;
  inset: 2px;
  width: calc(100% - 4px);
  height: calc(100% - 4px);
  background: var(--superficie);
}
/* cover para que lo que se ve en el visor sea exactamente lo que se captura despues. */
.visor__video, .visor__previa { object-fit: cover; }
.visor__video[hidden], .visor__previa[hidden] { display: none; }

.visor__vacio { display: flex; align-items: center; justify-content: center; }
/* La silueta es el fondo del encuadre: nace del borde inferior y llena la caja, como en el
   arte. Dimensionada en % del visor y no en px, porque el visor es fluido. */
.visor__silueta {
  position: absolute;
  bottom: 0;
  left: 50%;
  translate: -50% 0;
  width: 76%;
  height: auto;
  color: rgba(1, 103, 253, 0.17);
}
/* La camara va encima y algo mas abajo del centro: sobre el pecho de la silueta, como en
   el arte, no flotando encima de la cabeza. */
.visor__camara {
  position: relative;
  z-index: 1;
  translate: 0 16%;
  color: var(--neon);
  filter: var(--glow);
  display: flex;
}

/* Esquinas de encuadre, como las del marco del arte. */
.visor__esq {
  position: absolute;
  width: 34px; height: 34px;
  border: 2px solid var(--neon);
  filter: var(--glow);
  z-index: 2;
  pointer-events: none;
  /* Sin esto heredan el chaflan octogonal del contenedor y en vez de corchetes salen
     cuatro rombos: el recorte se come justo las dos aristas que forman la escuadra. */
  clip-path: none;
}
.visor__esq--tl { top: 16px; left: 16px; border-right: 0; border-bottom: 0; }
.visor__esq--tr { top: 16px; right: 16px; border-left: 0; border-bottom: 0; }
.visor__esq--bl { bottom: 16px; left: 16px; border-right: 0; border-top: 0; }
.visor__esq--br { bottom: 16px; right: 16px; border-left: 0; border-top: 0; }

/* Tache para descartar la foto (tomada o subida) o cancelar la camara abierta. */
.visor__tache {
  position: absolute;
  top: 12px;
  right: 12px;
  z-index: 4;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border-radius: 999px; /* unico circulo del sistema: es un control flotante, no una caja */
  background: rgba(0, 3, 10, 0.72);
  border: 2px solid var(--neon);
  color: var(--blanco);
  clip-path: none;
  filter: var(--glow);
  transition: transform var(--dur-instant) var(--ease-out), background var(--dur-instant) var(--ease-out);
}
.visor__tache:active { transform: scale(0.9); background: var(--neon); }
.visor__tache[hidden] { display: none; }

/* Cuenta atras 3-2-1 sobre el visor. */
.cuenta {
  position: absolute;
  inset: 2px;
  z-index: 5;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(0, 3, 10, 0.45);
  clip-path: none;
  pointer-events: none;
}
.cuenta[hidden] { display: none; }
.cuenta span {
  font-size: clamp(6rem, 34vw, 11rem);
  font-weight: 800;
  font-stretch: 70%;
  line-height: 1;
  color: var(--blanco);
  text-shadow: 0 0 40px rgba(1, 103, 253, 0.9);
  transform: skewX(-9deg);
}
/* Un ciclo por numero: entra grande y se encoge. La animacion se reinicia desde JS
   quitando y volviendo a poner esta clase con un reflow en medio. */
.cuenta span.is-late { animation: late 700ms var(--ease-out) both; }
@keyframes late {
  0%   { opacity: 0; transform: skewX(-9deg) scale(1.5); }
  18%  { opacity: 1; transform: skewX(-9deg) scale(1); }
  100% { opacity: 0.85; transform: skewX(-9deg) scale(0.92); }
}

.visor__barra {
  position: absolute; left: 2px; right: 2px; bottom: 2px; height: 5px;
  background: rgba(255, 255, 255, 0.12); z-index: 3; clip-path: none;
}
.visor__barra i {
  display: block; height: 100%; width: var(--progreso, 0%);
  background: var(--neon);
  transition: width var(--dur-toggle) var(--ease-out);
}

/* ---------------------------------------------------------------- QR */

/* Ancho del bloque del QR: acotado en px para que en pantallas grandes no crezca hasta
   ocupar media pagina y para que en el celular quepan debajo el folio, la leyenda y los
   dos botones sin obligar a hacer scroll. */
/* Aire generoso alrededor del codigo: el QR, el folio y la leyenda son tres cosas
   distintas y pegadas se leian como un bloque. */
.qr {
  display: flex;
  justify-content: center;
  margin-top: var(--space-4);
  margin-bottom: var(--space-6);
}
.qr__marco { width: min(100%, 250px); }

.qr__marco {
  --c: var(--corte-grande);
  position: relative;
  padding: 14px;
  background: var(--borde);
  clip-path: polygon(
    var(--c) 0, calc(100% - var(--c)) 0, 100% var(--c), 100% calc(100% - var(--c)),
    calc(100% - var(--c)) 100%, var(--c) 100%, 0 calc(100% - var(--c)), 0 var(--c)
  );
  filter: var(--glow-fuerte);
}
.qr__marco .visor__esq { width: 26px; height: 26px; top: 6px; left: 6px; }
.qr__marco .visor__esq--tr { left: auto; right: 6px; }
.qr__marco .visor__esq--bl { top: auto; bottom: 6px; }
.qr__marco .visor__esq--br { top: auto; bottom: 6px; left: auto; right: 6px; }

/* Placa BLANCA bajo el QR. No es una decision estetica: los lectores fallan con codigos
   sobre fondo oscuro o transparente.
   El tamaño se controla desde el contenedor y el canvas va al 100%. Dandole el ancho al
   propio canvas no funciona: es un elemento reemplazado con tamaño intrinseco (624 px) y
   acaba mandando el `max-width: 100%` del reset, no el ancho que se le pide. */
/* El cuadrado lo impone la PLACA, no el canvas.
   En un <canvas>, ni height:auto ni aspect-ratio conservan la proporcion como en un <img>:
   gana el alto intrinseco del atributo (624 px) y el codigo sale estirado en vertical. Con
   una placa de proporcion fija y el canvas a 100%/100% el resultado es exacto siempre. */
.qr__placa {
  background: #ffffff;
  padding: 10px;
  line-height: 0; /* si no, el line-height del contenedor añade una franja bajo la imagen */
}
.qr__lienzo {
  display: block;
  width: 100%;
  height: auto; /* en un <img> esto SI conserva la proporcion; en un <canvas> no */
}

/* Nombre del visitante, con el mismo tratamiento que en el pase: condensado, muy pesado,
   inclinado y en mayusculas. Se encoge solo si el nombre es largo. */
.qr__nombre {
  text-align: center;
  font-size: clamp(1.5rem, 7.4vw, 2.25rem);
  font-weight: 800;
  font-stretch: 70%;
  line-height: 1.05;
  color: var(--texto-fuerte);
  text-transform: uppercase;
  transform: skewX(-9deg);
  margin-bottom: var(--space-2);
  overflow-wrap: anywhere;
}
.qr__nombre:empty { display: none; }

.qr__folio {
  text-align: center;
  font-family: var(--font-mono);
  font-size: var(--text-1);
  font-weight: 700;
  letter-spacing: 0.1em;
  color: var(--azul-tenue);
  margin-bottom: var(--space-6);
}
.qr__folio strong {
  color: var(--texto-fuerte);
  user-select: all; /* un toque largo lo selecciona entero para dictarlo o copiarlo */
}

/* En rojo por decision del cliente: es la instruccion que no se puede pasar por alto.
   Se usa el rojo del sistema y en negrita, para que sobre el fondo negro llegue a 6.4:1 de
   contraste y no se lea como un error de formulario. */
/* En rojo por decision del cliente: es la instruccion que no se puede pasar por alto.
   Se usa el rojo del sistema, en cuerpo pequeño para que no compita con el folio. */
.qr__leyenda {
  text-align: center;
  font-size: var(--text--1);
  font-weight: 700;
  line-height: 1.45;
  color: var(--rojo);
  padding: 0 var(--space-3);
  max-width: 34ch;
  margin-inline: auto;
}

/* ---------------------------------------------------------------- check point */

.progreso { margin-top: var(--space-5); }
.progreso__titulo {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  margin-bottom: var(--space-3);
  font-size: var(--text--1);
  font-weight: 800;
  font-stretch: 84%;
  letter-spacing: 0.18em;
  color: var(--texto-apoyo);
}

/* Tarjeta de la actividad. Nace apagada (contorno tenue, trofeo gris, guion en los puntos)
   y al completarse pasa a neon con la puntuacion como protagonista. El salto entre los dos
   estados tiene que ser evidente de un vistazo: es la recompensa de haber hecho la dinamica. */
.reto {
  --c: var(--corte);
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  background: var(--superficie-alta);
  clip-path: polygon(
    var(--c) 0, calc(100% - var(--c)) 0, 100% var(--c), 100% calc(100% - var(--c)),
    calc(100% - var(--c)) 100%, var(--c) 100%, 0 calc(100% - var(--c)), 0 var(--c)
  );
  color: var(--texto-apoyo);
  transition: color var(--dur-toggle) var(--ease-out), background var(--dur-toggle) var(--ease-out);
}
/* El contorno va en un pseudo-elemento para poder recortarlo con el mismo chaflan: un
   `border` normal no sigue el clip-path y se ve cortado en las esquinas. */
.reto::before {
  content: "";
  position: absolute;
  inset: 0;
  clip-path: inherit;
  background: color-mix(in srgb, var(--azul-tenue) 24%, transparent);
  z-index: -2;
}
.reto::after {
  content: "";
  position: absolute;
  inset: 1.5px;
  clip-path: inherit;
  background: var(--superficie-alta);
  z-index: -1;
}

.reto__ico {
  position: relative;
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 52px;
  height: 52px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--azul-tenue) 12%, transparent);
  color: var(--texto-apoyo);
}
.reto__ico-base, .reto__ico-ok { grid-area: 1 / 1; display: flex; }
/* La palomita solo existe completado; si no, el trofeo ya se leeria como logrado. */
.reto__ico-ok { display: none; }

.reto__cuerpo { flex: 1 1 auto; display: flex; flex-direction: column; min-width: 0; }
.reto__nombre {
  font-size: var(--text-0);
  font-weight: 800;
  font-stretch: 80%;
  letter-spacing: 0.08em;
  color: var(--texto);
}
.reto__estado { font-size: var(--text--1); }

.reto__puntos {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  line-height: 1;
  min-width: 54px;
}
.reto__puntos strong {
  font-size: var(--text-3);
  font-weight: 800;
  font-stretch: 74%;
  color: var(--texto-apoyo);
  transition: color var(--dur-toggle) var(--ease-out);
}
.reto__pts { font-size: 0.625rem; letter-spacing: 0.16em; opacity: 0.7; margin-top: 2px; }

/* ---- completado: azul luminoso ---- */
.reto.is-hecho {
  color: var(--neon);
  background: color-mix(in srgb, var(--neon) 14%, var(--superficie-alta));
  /* El halo va en la tarjeta entera, no solo en el borde: es lo que la hace "luminosa"
     de un vistazo desde lejos, que es como se mira un movil en un evento. */
  box-shadow: 0 0 18px color-mix(in srgb, var(--neon) 42%, transparent),
              0 0 44px color-mix(in srgb, var(--neon) 22%, transparent);
}
.reto.is-hecho::before { background: var(--neon); }
.reto.is-hecho::after { background: color-mix(in srgb, var(--neon) 14%, var(--superficie-alta)); }
.reto.is-hecho .reto__ico {
  background: var(--neon);
  color: var(--blanco);
  box-shadow: 0 0 16px color-mix(in srgb, var(--neon) 75%, transparent);
}
.reto.is-hecho .reto__ico-base { display: none; }
.reto.is-hecho .reto__ico-ok { display: flex; }
.reto.is-hecho .reto__nombre { color: var(--blanco); }
.reto.is-hecho .reto__puntos strong {
  color: var(--neon);
  text-shadow: 0 0 16px color-mix(in srgb, var(--neon) 80%, transparent);
}

/* Encendido: solo la primera vez que pasa a completado, para que el visitante que esta
   mirando la pantalla vea el momento. La clase la quita el JS al terminar. */
.reto.is-encendiendo { animation: enciende var(--dur-modal) var(--ease-out-strong); }
@keyframes enciende {
  0%   { transform: scale(1); }
  45%  { transform: scale(1.045); }
  100% { transform: scale(1); }
}

/* ---------------------------------------------------------------- scanner (staff) */

.enlace--tenue { color: var(--texto-apoyo); font-size: var(--text--1); opacity: 0.75; }

.escaner { display: flex; flex-direction: column; gap: var(--space-3); }
.escaner__caja { aspect-ratio: 1 / 1; } /* cuadrado: es un lector, no un retrato */
/* Fondo oscuro explicito: mientras la camara arranca, sin esto se ve el azul del marco a
   toda la caja y parece un error. */
.escaner__caja .visor__video { object-fit: cover; background: var(--negro); }

.ficha { display: flex; flex-direction: column; }
.ficha__nombre {
  text-align: center;
  font-size: var(--text-3);
  font-weight: 800;
  font-stretch: 70%;
  line-height: 1.05;
  color: var(--texto-fuerte);
  text-transform: uppercase;
  transform: skewX(-9deg);
  overflow-wrap: anywhere;
}
.ficha__folio {
  text-align: center;
  font-family: var(--font-mono);
  font-size: var(--text-1);
  letter-spacing: 0.1em;
  color: var(--azul-tenue);
  margin-bottom: var(--space-3);
}
.ficha__datos { padding-inline: 0; }
.ficha__etiqueta {
  display: block;
  margin: var(--space-4) 0 var(--space-2);
  font-size: var(--text--1);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--texto-apoyo);
  font-weight: 700;
}
/* El numero de puntos es el dato que el staff teclea a toda prisa: grande y centrado. */
.ficha input[type="number"] {
  text-align: center;
  font-size: var(--text-3);
  font-weight: 800;
  letter-spacing: 0.04em;
  -moz-appearance: textfield;
}
.ficha input[type="number"]::-webkit-outer-spin-button,
.ficha input[type="number"]::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }

/* ---------------------------------------------------------------- confirmacion */

.confirma {
  --c: var(--corte-grande);
  width: min(92vw, 380px);
  max-height: 88svh;
  overflow-y: auto;
  /* margin auto explicito: al cambiar el display a flex en [open], el centrado que trae el
     navegador para un dialogo modal deja de aplicarse y la caja se pega arriba. */
  margin: auto;
  padding: 2px; /* el trazo del marco neon */
  border: 0;
  background: var(--borde);
  color: var(--texto);
  clip-path: polygon(
    var(--c) 0, calc(100% - var(--c)) 0, 100% var(--c), 100% calc(100% - var(--c)),
    calc(100% - var(--c)) 100%, var(--c) 100%, 0 calc(100% - var(--c)), 0 var(--c)
  );
}
/* El relleno va en un pseudo-elemento para no meter otro div en el markup. */
.confirma::before {
  content: "";
  position: absolute;
  inset: 2px;
  background: var(--superficie);
  clip-path: inherit;
  z-index: -1;
}
.confirma::backdrop { background: rgba(0, 3, 10, 0.82); }
.confirma[open] { display: flex; flex-direction: column; gap: var(--space-3); }

.confirma__titulo {
  font-size: var(--text-2);
  font-weight: 800;
  font-stretch: 74%;
  letter-spacing: 0.02em;
  text-align: center;
  color: var(--texto-fuerte);
  transform: skewX(-9deg);
  padding: var(--space-5) var(--space-5) 0;
}
.confirma__nota {
  font-size: var(--text--1);
  color: var(--texto-apoyo);
  text-align: center;
  padding: 0 var(--space-5);
}

.confirma__lista { display: flex; flex-direction: column; padding: var(--space-2) var(--space-5); }
.confirma__lista > div {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--space-4);
  padding: var(--space-3) 0;
  border-bottom: 1px solid color-mix(in srgb, var(--neon) 22%, transparent);
}
.confirma__lista > div:last-child { border-bottom: 0; }
.confirma__lista dt {
  flex: 0 0 auto;
  font-size: var(--text--1);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--texto-apoyo);
}
.confirma__lista dd {
  flex: 1 1 auto;
  min-width: 0;
  text-align: right;
  font-weight: 700;
  color: var(--texto-fuerte);
  /* Un correo largo no debe ensanchar el dialogo ni salirse. */
  overflow-wrap: anywhere;
}

.confirma__acciones {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-5) var(--space-5);
}

/* ---------------------------------------------------------------- exito */

.exito { display: flex; flex-direction: column; align-items: center; gap: var(--space-3); text-align: center; padding-top: var(--space-6); }
.exito[hidden] { display: none; }
.exito__ico { color: var(--neon); filter: var(--glow); display: flex; }
.exito__titulo { font-size: var(--text-2); font-weight: 800; font-stretch: 82%; color: var(--texto-fuerte); }
.exito__txt { font-size: var(--text-0); color: var(--texto); }
.exito__nota { font-size: var(--text--1); color: var(--texto-apoyo); max-width: 30ch; }
.exito .boton { margin-top: var(--space-4); align-self: stretch; }

/* ---------------------------------------------------------------- confirmacion staff */

.exitazo {
  position: fixed;
  inset: 0;
  z-index: 50;
  display: grid;
  place-items: center;
  /* Casi opaco: con un velo mas ligero, el texto de la pantalla de debajo se colaba entre
     el titulo y el detalle y el aviso se leia sucio. */
  background: rgba(0, 3, 10, 0.965);
  animation: aparece var(--dur-toggle) var(--ease-out) both;
}
.exitazo__caja {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-6);
  text-align: center;
  animation: entraExito var(--dur-modal) var(--ease-out-strong) both;
}
.exitazo__ico {
  display: grid;
  place-items: center;
  width: 108px;
  height: 108px;
  border-radius: 999px;
  background: var(--neon);
  color: var(--blanco);
  box-shadow: 0 0 26px color-mix(in srgb, var(--neon) 80%, transparent),
              0 0 70px color-mix(in srgb, var(--neon) 45%, transparent);
}
.exitazo__titulo {
  font-size: var(--text-2);
  font-weight: 800;
  font-stretch: 76%;
  letter-spacing: 0.04em;
  color: var(--blanco);
  transform: skewX(-9deg);
}
.exitazo__detalle { font-size: var(--text-0); color: var(--texto-apoyo); max-width: 26ch; }

/* Variante de BLOQUEO: algo que no se puede hacer. Mismo formato para que el operador lo
   lea igual de rapido, pero en rojo y con el icono de alerta: el color es lo primero que
   se procesa a distancia, antes que el texto. */
.exitazo__ico--no { display: none; }
.exitazo.is-bloqueo .exitazo__ico--ok { display: none; }
.exitazo.is-bloqueo .exitazo__ico--no { display: grid; }
.exitazo.is-bloqueo .exitazo__ico {
  background: var(--rojo);
  box-shadow: 0 0 26px color-mix(in srgb, var(--rojo) 80%, transparent),
              0 0 70px color-mix(in srgb, var(--rojo) 45%, transparent);
}
.exitazo.is-bloqueo .exitazo__detalle { color: var(--texto); }

@keyframes aparece { from { opacity: 0; } to { opacity: 1; } }
@keyframes entraExito {
  0%   { opacity: 0; transform: scale(0.86); }
  60%  { opacity: 1; transform: scale(1.03); }
  100% { opacity: 1; transform: scale(1); }
}

/* ---------------------------------------------------------------- licencia (staff) */

.lic { display: flex; flex-direction: column; }
.lic__previa {
  margin: var(--space-4) 0 0;
  padding: 2px;
  background: var(--borde);
  filter: var(--glow);
}
.lic__previa img { display: block; width: 100%; height: auto; background: var(--negro); }

/* La hoja de impresion vive fuera del marco 9:16 y solo existe al imprimir. */
.hoja { display: none; }

@media print {
  /* Tarjeta CR80: es el formato que carga la Zebra. Sin margenes: la impresora de tarjetas
     imprime a sangre y cualquier margen desplaza el arte. */
  @page { size: 85.6mm 54mm; margin: 0; }

  html, body { background: #fff !important; margin: 0; padding: 0; }
  /* Se oculta TODA la aplicacion y se deja solo la hoja. El aviso de exito vive fuera del
     marco, asi que hay que nombrarlo aparte o se colaria en la tarjeta impresa. */
  .marco, .noscript, .exitazo { display: none !important; }

  .hoja {
    display: block !important;
    width: 85.6mm;
    height: 54mm;
    margin: 0;
    padding: 0;
  }
  .hoja img {
    display: block;
    width: 85.6mm;
    height: 54mm;
    object-fit: fill; /* el arte YA tiene la proporcion de la tarjeta */
  }
  /* Sin esto, Chrome no imprime fondos ni imagenes de fondo y la tarjeta sale en blanco. */
  * { -webkit-print-color-adjust: exact !important; print-color-adjust: exact !important; }
}

/* ---------------------------------------------------------------- accesibilidad */

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* Pantallas muy bajas (iPhone SE apaisado, teclado abierto): se recorta el aire, no el
   contenido. El titular es lo primero que sobra cuando no cabe todo. */
@media (max-height: 700px) {
  .titular { font-size: var(--text-3); }
  .sub { margin-bottom: var(--space-4); }
  .marca__logo { width: 108px; }
  .campo__caja { min-height: 52px; }
  .boton { min-height: 56px; }
}


/* ---------------------------------------------------------------- tipos de licencia */

/* Dos columnas: las cuatro caben sin scroll en un celular y se comparan de un vistazo,
   que es justo lo que hace falta para elegir. */
.tipos {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-3);
  margin-top: var(--space-2);
}

.tipo {
  --c: var(--corte);
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: 2px;
  background: color-mix(in srgb, var(--azul-tenue) 30%, transparent);
  clip-path: polygon(
    var(--c) 0, calc(100% - var(--c)) 0, 100% var(--c), 100% calc(100% - var(--c)),
    calc(100% - var(--c)) 100%, var(--c) 100%, 0 calc(100% - var(--c)), 0 var(--c)
  );
  transition: background var(--dur-toggle) var(--ease-out), filter var(--dur-toggle) var(--ease-out),
              transform var(--dur-instant) var(--ease-out);
}
.tipo::before {
  content: "";
  position: absolute;
  inset: 2px;
  background: var(--superficie-alta);
  clip-path: inherit;
  z-index: -1;
}
.tipo img {
  width: 100%;
  height: auto;
  /* La miniatura es el arte real de la tarjeta: se ve lo que se va a llevar impreso. */
  clip-path: inherit;
}
.tipo__nombre {
  padding: 0 var(--space-2) var(--space-2);
  font-size: var(--text--1);
  font-weight: 800;
  font-stretch: 80%;
  letter-spacing: 0.1em;
  text-align: center;
  color: var(--texto-apoyo);
  transform: skewX(-7deg);
}
.tipo:active { transform: scale(0.978); }

/* Elegida: se enciende en neon y el resto se apaga, para que no haya duda de cual esta
   seleccionada cuando el visitante levanta la vista. */
.tipo.is-elegida { background: var(--neon); filter: var(--glow); }
.tipo.is-elegida .tipo__nombre { color: var(--blanco); }
.tipos.is-eligiendo .tipo:not(.is-elegida) { opacity: 0.55; }

/* El bloque de eleccion es compacto (titular + 4 tarjetas + boton) y no llena el alto del
   celular. Con el `margin-top:auto` de .acciones el boton se iba al borde inferior y dejaba
   un hueco muerto de medio pantallazo entre las tarjetas y el. Aqui se centra el conjunto y
   el boton queda a una distancia fija de las tarjetas, que es lo que espera el pulgar. */
[data-pantalla="tipo"] { justify-content: center; }
[data-pantalla="tipo"] .acciones { margin-top: var(--space-5); padding-top: 0; }

/* ================================================================ 5 SECONDS

   El juego del kiosko. Aqui casi no se dibuja nada: el cliente entrego cada pantalla ya
   compuesta (escena + texto de la pregunta, que es fijo), los tres botones sueltos con alfa
   y las treinta tarjetas de opcion con su tipografia y su icono. Este bloque solo las
   COLOCA. Lo unico que se dibuja de verdad es el reloj, porque tiene que estar vivo.

   Todo escala con svh y no con los pasos --text-*: esos estan calibrados para el ancho de un
   celular (360-480 px) y en la pantalla del stand, que es 1080x1920, se quedaban diminutos.
   Como el marco es 9:16 y su alto es 100svh, una escala en svh sirve igual en el monitor del
   stand que en un movil de prueba.

   Las posiciones salen de los mockups del cliente, medidas sobre el lienzo de 941x1672.
   ============================================================================ */

.pantalla--juego {
  padding: 0;
  overflow: hidden;
  background-image: none; /* cada pantalla trae su propia escena */
  /* Kiosko: nadie va a seleccionar texto ni a hacer doble toque para acercar, y las dos
     cosas estorban cuando lo unico que se hace es tocar una tarjeta contra reloj. */
  user-select: none;
  -webkit-user-select: none;
  touch-action: manipulation;
}

/* La entrada escalonada de .pantalla.is-activa > * animaria tambien la escena, y el fondo
   entraria flotando. Aqui cada vista maneja su propia transicion. */
.pantalla--juego.is-activa > * { animation: none; }

.jg__escena {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center center;
}

.jg__vista {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-end;
  padding: 8svh var(--space-4) 4svh;
  opacity: 0;
  transform: translate3d(0, 1.4svh, 0);
  pointer-events: none;
  transition: opacity var(--dur-toggle) var(--ease-out), transform var(--dur-modal) var(--ease-out-strong);
}
.jg__vista[hidden] { display: none; }
.jg__vista.is-activa { opacity: 1; transform: none; pointer-events: auto; }

/* ---------------------------------------------------------------- los botones

   El MARCO se dibuja aqui; del arte del cliente solo viene el lettering.

   Los tres botones que entrego no son la misma pieza: sus marcos median 3.25, 3.81 y 3.95
   de proporcion, el chaflan y el grosor del trazo cambiaban de uno a otro, el de INICIAR
   estaba algo torcido y en el de FINALIZAR el texto iba corrido a la izquierda. Dibujado,
   el marco sale identico en los tres, geometricamente exacto, nitido a cualquier tamano y
   con estados de verdad al pulsar. Lo que no se puede reproducir es la tipografia, y esa si
   es del cliente (ver herramientas/botones_juego.py).

   Es el mismo lenguaje de forma que las tarjetas: chaflan en las cuatro esquinas, borde
   neon de 3 px y relleno oscuro, con el borde hecho por el padre y el relleno por un
   pseudo-elemento — un `border` normal no sigue al clip-path y deja las esquinas cuadradas. */

.jg__btn {
  --c: clamp(9px, 2.1svh, 28px);
  position: relative;
  display: grid;
  place-items: center;
  width: min(58%, 440px);
  aspect-ratio: 3.6;
  flex: 0 0 auto;
  clip-path: polygon(
    var(--c) 0, calc(100% - var(--c)) 0, 100% var(--c), 100% calc(100% - var(--c)),
    calc(100% - var(--c)) 100%, var(--c) 100%, 0 calc(100% - var(--c)), 0 var(--c)
  );
  background: var(--neon);
  filter: var(--glow);
  transition: transform var(--dur-instant) var(--ease-out), filter var(--dur-toggle) var(--ease-out);
}
.jg__btn::before {
  content: "";
  position: absolute;
  inset: 3px;
  clip-path: inherit;
  background: linear-gradient(180deg, rgba(4, 16, 40, 0.96) 0%, rgba(0, 4, 12, 0.98) 100%);
}
/* Los tres lettering comparten lienzo, asi que un solo ancho basta para que las tres
   palabras salgan del mismo cuerpo y centradas. */
.jg__btn img { position: relative; width: 84%; height: auto; }
.jg__btn:active { transform: scale(0.972); filter: var(--glow-fuerte) brightness(1.12); }
/* En el resultado el boton se queda al pie y el espacio sobrante deja ver la escena. */
.jg__btn--pie { margin-top: auto; }

/* ---------------------------------------------------------------- el reloj

   Es la unica pieza que se dibuja, porque es la unica que cambia sesenta veces por segundo.
   Va en el hueco que el arte del cliente deja libre arriba. */

.reloj {
  position: relative;
  display: grid;
  place-items: center;
  width: clamp(120px, 20svh, 372px);
  aspect-ratio: 1;
  flex: 0 0 auto;
  margin-bottom: 1.8svh;
}
.reloj__anillo {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  transform: rotate(-90deg); /* la cuenta arranca arriba, no a las tres */
}
/* Corona de marcas. Es un circulo con el trazo discontinuo: 60 marcas repartidas en su
   perimetro (2 * PI * 63 = 395.84, entre 60 son 6.597 por marca). Sale mas barato y mas
   nitido que sesenta <line> en el HTML. */
.reloj__ticks {
  fill: none;
  stroke: color-mix(in srgb, var(--neon) 42%, transparent);
  stroke-width: 7;
  stroke-dasharray: 1.7 4.897;
}
.reloj__pista {
  fill: none;
  stroke: color-mix(in srgb, var(--neon) 16%, transparent);
  stroke-width: 3;
}
.reloj__barra {
  fill: none;
  stroke: var(--neon);
  stroke-width: 6;
  stroke-linecap: round;
  /* 2 * PI * 54, el radio del <circle> del HTML. El JS solo mueve el offset, que es pintado
     sobre un SVG de 200 px y no provoca layout. */
  stroke-dasharray: 339.292;
  stroke-dashoffset: 0;
  filter: var(--glow-fuerte);
  transition: stroke var(--dur-toggle) var(--ease-out);
}
.reloj__n {
  grid-area: 1 / 1;
  font-weight: 800;
  font-stretch: 70%;
  font-size: clamp(2rem, 6.8svh, 7.2rem);
  line-height: 1;
  color: var(--texto-fuerte);
  font-variant-numeric: tabular-nums; /* sin esto, al pasar de 05 a 04 salta el ancho */
  transform: translateY(-0.1em);
  text-shadow: 0 0 26px rgba(1, 103, 253, 0.45);
}
.reloj__txt {
  grid-area: 1 / 1;
  align-self: center;
  transform: translateY(2.9svh);
  font-weight: 700;
  font-stretch: 75%;
  font-size: clamp(0.55rem, 1.2svh, 1.3rem);
  letter-spacing: 0.42em;
  color: var(--azul-texto);
}
/* Ultimo segundo: el numero late. Solo transform, y se apaga con prefers-reduced-motion. */
.reloj.is-urgente .reloj__n { animation: late 600ms var(--ease-in-out) infinite; }
.reloj.is-urgente .reloj__barra { stroke: var(--rojo); filter: drop-shadow(0 0 14px var(--rojo)); }
.reloj.is-urgente .reloj__ticks { stroke: color-mix(in srgb, var(--rojo) 45%, transparent); }
@keyframes late {
  0%, 100% { transform: translateY(-0.06em) scale(1); }
  50%      { transform: translateY(-0.06em) scale(1.09); }
}

/* ---------------------------------------------------------------- las 6 opciones */

.jg__vista--opciones { justify-content: flex-start; padding: 6.5svh 4.5% 4svh; }

.ops {
  display: grid;
  grid-template-columns: 1fr 1fr;
  /* Filas explicitas: con `auto` la ultima se comprime y las tarjetas dejan de ser iguales,
     que es justo lo que no puede pasar cuando se elige contra reloj. */
  grid-template-rows: repeat(3, 1fr);
  gap: clamp(8px, 1.5svh, 24px);
  width: 100%;
  max-width: 940px;
  flex: 1 1 auto;
  min-height: 0;
  place-items: center;
  align-content: center;
}

/* El MARCO se dibuja aqui; del arte del cliente solo viene el icono y el texto.

   El cliente dibujo las tarjetas con proporciones, chaflanes y grosores distintos por lote
   (de 1.08 en la pregunta 3 a 2.47 en la 5), asi que recortadas no casan de ninguna manera:
   escalarlas al mismo lienzo deja las apaisadas con la mitad de altura, y estirarles el
   marco deja el chaflan corto y el borde fino. Dibujado, el marco sale identico en las
   treinta. Es la misma solucion que en los botones, y el mismo lenguaje de forma: chaflan en
   las cuatro esquinas, borde neon y relleno, con el borde hecho por el padre y el relleno
   por un pseudo-elemento — un `border` normal no sigue al clip-path y deja las esquinas
   cuadradas. */

.op {
  --c: clamp(10px, 2.4svh, 36px);
  position: relative;
  display: grid;
  place-items: center;
  width: 100%;
  /* La celda toma la proporcion del contenido: asi el marco encaja con lo que lleva dentro
     y las seis salen exactamente iguales. El aire ya viene en el propio lienzo. */
  aspect-ratio: 620 / 560;
  height: auto;
  max-height: 100%;
  min-height: 0;
  clip-path: polygon(
    var(--c) 0, calc(100% - var(--c)) 0, 100% var(--c), 100% calc(100% - var(--c)),
    calc(100% - var(--c)) 100%, var(--c) 100%, 0 calc(100% - var(--c)), 0 var(--c)
  );
  background: var(--neon);
  filter: var(--glow);
  transition: transform var(--dur-instant) var(--ease-out), filter var(--dur-toggle) var(--ease-out),
              opacity var(--dur-toggle) var(--ease-out);
}
.op::before {
  content: "";
  position: absolute;
  inset: 4px;
  clip-path: inherit;
  background: linear-gradient(160deg, rgba(8, 22, 52, 0.97) 0%, rgba(2, 8, 20, 0.99) 55%, rgba(0, 3, 10, 1) 100%);
}
/* Las treinta capas de contenido comparten lienzo, asi que un solo tamano basta para que
   todas caigan en el mismo sitio y con el mismo cuerpo. */
.op img {
  position: relative;
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.op:active { transform: scale(0.968); }
/* Al elegir, la tocada se enciende y las demas se apagan: el visitante tiene que ver QUE
   toco antes de que la pantalla cambie. */
.op.is-elegida { transform: scale(1.03); filter: var(--glow-fuerte) brightness(1.12); }
.ops.is-resuelta .op:not(.is-elegida) { opacity: 0.25; }
.ops.is-resuelta .op { pointer-events: none; }

/* ---------------------------------------------------------------- resultado */

/* El resultado cierra con el badge y el marcador, y nada mas. El bloque se apoya arriba,
   bajo el titular del arte, y el boton se queda al pie.

   El marcador se tine del color del tipo que salio. Los cuatro colores estan muestreados de
   los iconos que entrego el cliente, no elegidos a ojo: son los de su marca. */
.jg__vista--final { --acento: var(--neon); justify-content: flex-start; padding-top: 17svh; }
.jg__vista--final[data-tipo="adventure"] { --acento: #1dfc01; }
.jg__vista--final[data-tipo="sport"]     { --acento: #fc0118; }
.jg__vista--final[data-tipo="urban"]     { --acento: #00ccfe; }
.jg__vista--final[data-tipo="work"]      { --acento: #feed00; }
/* El resultado cae sobre la ciudad iluminada del arte, y ahi el marcador y el pie del pase
   pierden el contraste justo cuando hay que leerlos. Un velo suave y centrado los sostiene
   sin apagar la escena, que es lo que hace bonita a esta pantalla. */
.jg__vista--final::before {
  content: "";
  position: absolute;
  inset: 28% 0 4%;
  background: radial-gradient(62% 54% at 50% 42%, rgba(0, 3, 10, 0.84) 0%, rgba(0, 3, 10, 0.55) 58%, transparent 100%),
              linear-gradient(0deg, rgba(0, 3, 10, 0.8) 0%, rgba(0, 3, 10, 0.35) 22%, transparent 42%);
  pointer-events: none;
}
.jg__vista--final > * { position: relative; }

/* El badge del tipo de rider, recortado del arte de las licencias del cliente (ver
   herramientas/badges_rider.py). Va en el hueco hexagonal que el arte del resultado deja
   libre, asi que su ancho lo marca ese hueco. */
.rider {
  width: min(86%, 660px);
  height: auto;
  flex: 0 0 auto;
  filter: drop-shadow(0 0 18px rgba(1, 103, 253, 0.35));
}
.rider[hidden] { display: none; }

/* El marcador. Manda la cifra: es lo que el visitante se lleva y lo que compara con quien
   venga detras. Los cinco puntos dan la lectura de un vistazo, sin tener que leer un numero.
   El pase queda debajo y en pequeno: ahi ya no hay nada que el visitante tenga que hacer,
   solo el codigo que el staff necesita. */

.marcador {
  display: grid;
  place-items: center;
  gap: 0.6svh;
  margin-top: 4.5svh;
}
.marcador__cifra {
  display: flex;
  align-items: baseline;
  font-weight: 800;
  font-stretch: 68%;
  line-height: 1;
  color: var(--texto-fuerte);
  font-variant-numeric: tabular-nums;
  font-size: clamp(3rem, 9.5svh, 8.5rem);
  text-shadow: 0 0 34px color-mix(in srgb, var(--acento) 40%, transparent);
}
.marcador__total {
  font-size: 0.44em;
  color: var(--acento);
  margin-left: 0.06em;
}
.marcador__txt {
  font-weight: 700;
  font-stretch: 78%;
  font-size: clamp(0.75rem, 1.7svh, 1.7rem);
  letter-spacing: 0.42em;
  text-indent: 0.42em; /* compensa el tracking del ultimo caracter para que quede centrado */
  text-transform: uppercase;
  color: var(--texto-apoyo);
}

/* Un punto por pregunta: encendidos los aciertos. No dice CUALES se fallaron — eso solo
   invitaria a discutir la respuesta con el staff — sino cuantos. */
.marcador__puntos {
  display: flex;
  gap: clamp(6px, 1.1svh, 14px);
  margin-top: 1.4svh;
}
.marcador__punto {
  width: clamp(10px, 1.7svh, 22px);
  aspect-ratio: 1;
  transform: rotate(45deg);
  background: color-mix(in srgb, var(--acento) 16%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--acento) 38%, transparent);
}
.marcador__punto.is-dado {
  background: var(--acento);
  box-shadow: none;
  filter: drop-shadow(0 0 3px color-mix(in srgb, var(--acento) 70%, transparent))
          drop-shadow(0 0 14px color-mix(in srgb, var(--acento) 45%, transparent));
}

/* ---------------------------------------------------------------- estacion del staff */

/* La lista de partidas que el staff elige. Cada fila lleva lo unico que sirve para
   reconocerla desde la pantalla del kiosko: el marcador, el tipo y hace cuanto fue. */

.elige { display: flex; flex-direction: column; gap: var(--space-3); }
.elige__lista { display: flex; flex-direction: column; gap: var(--space-2); margin-top: var(--space-2); }

.elige__op {
  --c: 10px;
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: var(--space-3);
  width: 100%;
  min-height: 62px;
  padding: var(--space-3) var(--space-4);
  text-align: left;
  clip-path: polygon(
    var(--c) 0, calc(100% - var(--c)) 0, 100% var(--c), 100% calc(100% - var(--c)),
    calc(100% - var(--c)) 100%, var(--c) 100%, 0 calc(100% - var(--c)), 0 var(--c)
  );
  background: var(--borde);
  transition: transform var(--dur-instant) var(--ease-out), filter var(--dur-toggle) var(--ease-out);
}
.elige__op::before {
  content: "";
  position: absolute;
  inset: 2px;
  clip-path: inherit;
  background: var(--superficie-alta);
}
.elige__op { position: relative; }
.elige__op > * { position: relative; }
.elige__op:active { transform: scale(0.985); filter: var(--glow); }

.elige__marca {
  font-weight: 800;
  font-stretch: 72%;
  font-size: var(--text-2);
  color: var(--texto-fuerte);
  font-variant-numeric: tabular-nums;
}
.elige__tipo {
  font-weight: 700;
  font-stretch: 78%;
  font-size: var(--text-0);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--azul-texto);
}
.elige__hora { font-size: var(--text--1); color: var(--texto-apoyo); white-space: nowrap; }


.est__marca {
  margin-top: var(--space-4);
  font-weight: 700;
  font-stretch: 78%;
  font-size: var(--text-1);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--azul-texto);
}

@media (prefers-reduced-motion: reduce) {
  .reloj.is-urgente .reloj__n { animation: none; }
  .jg__vista { transition-duration: 1ms; }
}
