* { box-sizing: border-box; }

/* ---- ESCENARIO COMÚN -----------------------------------------------------------------
   El juego entero ocurre dentro de un mismo rectángulo: el logo del estudio, las cuentas,
   el menú, la partida y el resultado. Nada cambia de tamaño al pasar de una pantalla a
   otra — antes el menú ocupaba el alto completo del navegador y al empezar a jugar el
   juego "se achicaba" a un lienzo en el medio.

   El tamaño es FIJO (1024x576, 16:9) y el escenario entero se escala para llenar la
   ventana — ver stage.js. Esa es la única forma de cumplir las dos exigencias a la vez:
   que nada cambie de tamaño entre pantallas, y que NUNCA haga falta desplazar ni
   aparezcan barras. Si la ventana es chica no se recorta el contenido: se achica todo
   junto, en bloque.

   16:9 porque es la proporción en la que se juega en móvil apaisado, y la que mejor
   promedia entre un teléfono moderno (~19.5:9, sobra a los lados) y una tableta (4:3,
   sobra arriba y abajo). Coincide además con la del propio arena, 800x450. */
:root {
  --stage-w: 1024px;
  --stage-h: 576px;
  --stage-scale: 1;
  --stage-border: #2a2f4a;

  /* Las dos columnas del menú y de la pantalla de cuentas. Viven acá porque el logo se
     posiciona a partir de ellas (ver .menu-logo): si alguien cambia el ancho de una
     columna, el logo lo sigue solo en vez de quedar desalineado. */
  --col-izq: 300px;
  --col-der: 430px;
  --col-gap: 20px;
}

/* Las tres caras propias del juego. Se generan con devtools/fonts/build.js y son la única
   forma de que Windows, Android, iOS y macOS dibujen el mismo texto: antes acá se pedían
   'Segoe UI' y 'Consolas', que no existen fuera de Windows, y el escenario es un diseño
   fijo de 1024x576 donde un cambio de métricas descoloca lo que estaba medido al píxel.

   La reserva del sistema NO es decorativa: el nickname solo valida largo (auth.js:49-52),
   así que un jugador puede llamarse "Ramón" o escribir en japonés. Las caras propias
   cubren ASCII imprimible y el navegador resuelve carácter por carácter lo que falte. */
@font-face {
  font-family: 'BBA Titles';
  src: url('/assets/fonts/BBATitles.otf') format('opentype');
  font-display: block;
}
@font-face {
  font-family: 'BBA Body';
  src: url('/assets/fonts/BBABody.otf') format('opentype');
  font-display: block;
}
@font-face {
  font-family: 'BBA Score';
  src: url('/assets/fonts/BBAScore.otf') format('opentype');
  font-display: block;
}

html, body {
  margin: 0;
  height: 100%;
  background: #05060c;
  color: #f0f2ff;
  font-family: 'BBA Body', 'Segoe UI', system-ui, sans-serif;
  /* Las tres caras tienen UN solo grosor. Sin esto el navegador fabrica la negrita
     engordando el trazo, y sobre una tipografía de rejilla eso no da negrita: da un
     borrón. Hay `font-weight: 700/800/900` repartido por toda la hoja y no hace falta
     tocarlo — Titles ya viene con el peso dibujado. */
  font-synthesis: none;
  /* LO QUE DIBUJA EL NAVEGADOR Y EL CSS NO ALCANZA: el calendario del campo de fecha, la
     lista desplegada de un <select>, la marca de una casilla, la barra del chat. Ninguna de
     esas piezas se puede estilar — son ventanas del sistema — y por omisión salen CLARAS,
     porque el navegador supone una página clara mientras nadie le diga lo contrario.

     De ahí venía el peor síntoma de la pantalla de registro: el campo de fecha en blanco
     puro en medio de un panel de neón. `color-scheme` es la única declaración que gobierna
     esas piezas, y va acá —en la raíz— y no campo por campo, porque el juego es oscuro
     entero y no hay ni una pantalla clara de la que haga excepción.

     Deliberadamente NO cambia nada de lo que el juego dibuja por su cuenta: todo lo que
     tiene color declarado en esta hoja se ve exactamente igual. */
  color-scheme: dark;
}

/* Los controles de formulario NO heredan la familia: por defecto un <button>, un <input> o
   un <select> se dibujan con la del sistema aunque body diga otra cosa. Eso dejaba fuera
   del cambio a los botones del menú principal, a las pestañas de panel y a los paginadores
   — que son botones, no divs — sin ninguna señal de que algo faltaba.

   Se hereda solo la FAMILIA, no `font: inherit`: el atajo resetearía también tamaño y peso
   y desarmaría lo que cada regla ya tiene ajustado. */
button, input, select, textarea, optgroup, option {
  font-family: inherit;
}

/* La marca de una casilla la dibuja el sistema y sale con el azul del escritorio —el mismo
   en Windows, otro en macOS—, o sea un color que no está en la paleta del juego. `accent-color`
   es lo único que la gobierna. Son las dos casillas de "recordar" de la pantalla de cuentas. */
input[type="checkbox"], input[type="radio"] { accent-color: #4fd1ff; }

/* Nada desplaza NUNCA: el escenario siempre entra por construcción. */
html, body { overflow: hidden; }

body {
  display: flex;
  align-items: center;
  justify-content: center;
}

/* ---- REGLA DURA: DENTRO DEL ESCENARIO NO SE CONSULTA LA VENTANA ------------------------
   Una pantalla mide SIEMPRE 1024×576. No es un tamaño máximo ni un punto de partida: es el
   tamaño, en las cuatro plataformas y en las dos orientaciones. Lo único que cambia con la
   ventana es `--stage-scale`, o sea a qué zoom se ve ese mismo rectángulo.

   De ahí sale una consecuencia que no es obvia y que costó un defecto: **una `@media` de
   tamaño o de proporción de ventana no puede decidir nada de lo que hay acá adentro.** La
   ventana y el escenario dejaron de ser lo mismo el día que se fijó el tamaño de diseño; una
   consulta al ancho, al alto o al `aspect-ratio` de la ventana está preguntando por una caja
   que no es la que se está acomodando.

   Y no falla en silencio: al girar un teléfono a vertical, cuatro bloques `@media` reordenaban
   las pantallas para una disposición apilada que el escenario nunca adopta. En el menú el
   resultado era el logo dibujado ENCIMA de los botones, porque el logo va en posición absoluta
   —calculada a partir del ancho de la columna derecha— y una regla de rejilla no lo mueve.
   Se quitaron los cuatro el 2026-08-16. Si alguna vez hace falta una disposición distinta,
   el discriminante tiene que ser algo del escenario o del modo de entrada (`body.bba-tactil`),
   nunca la ventana.

   Las `@media` que SÍ siguen valiendo son las que no hablan de tamaño: `prefers-reduced-motion`
   y compañía preguntan por el usuario, no por la caja. */
.screen {
  width: var(--stage-w);
  height: var(--stage-h);
  flex: none;
  transform: scale(var(--stage-scale));
  transform-origin: center center;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 12px;
  border: 2px solid var(--stage-border);
  border-radius: 12px;
  position: relative;
  /* Si algo no entra es un defecto de diseño a corregir, no algo para desplazar. El
     verificador devtools/shot-stage.js lo detecta pantalla por pantalla. */
  overflow: hidden;
  text-align: center;
}

/* La pantalla visible recibe el foco por código (ver reclamarFoco en main.js) para que el
   teclado siga llegando al documento. Es foco programático, no de tabulación: el anillo
   sobraría. */
.screen:focus { outline: none; }

.screen.hidden { display: none; }
.hidden { display: none !important; }

#menu-screen h1 {
  font-size: 2.6rem;
  letter-spacing: 0.12em;
  margin-bottom: 0;
  background: linear-gradient(90deg, #4fd1ff, #7cff6b, #ff6bd6);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

/* Se acota por ALTO y no por ancho, y vale para las DOS pantallas que lo muestran
   (cuentas y menú). El archivo con transparencia está recortado al arte (721x793, casi
   cuadrado); el original traía un margen negro enorme (1536x1024), así que darle el
   mismo ancho de antes lo agrandaba más del doble — esa era la causa real de que el
   menú se sintiera gigante. */
.menu-logo {
  height: 240px;
  width: auto;
  max-width: 100%;
  margin-bottom: 0;
}

#splash-screen {
  background: #05060c;
}

.splash-logo {
  width: 340px;   /* 70vw de la ventana era; el escenario mide 1024 y el tope nunca llegaba a morder salvo en vertical */
  height: auto;
  opacity: 0;
  animation: splashFade 3.2s ease-in-out forwards;
}

@keyframes splashFade {
  0% { opacity: 0; }
  25% { opacity: 1; }
  70% { opacity: 1; }
  100% { opacity: 0; }
}

.subtitle {
  color: #9aa3c7;
  margin-top: 4px;
  margin-bottom: 32px;
}

#name-input {
  font-size: 1.1rem;
  padding: 10px 16px;
  border-radius: 8px;
  border: 1px solid #2a2f4a;
  background: #10132a;
  color: #f0f2ff;
  width: 260px;
  outline: none;
}

#name-input:focus { border-color: #4fd1ff; }

.primary-button {
  font-family: 'BBA Titles', 'Segoe UI', system-ui, sans-serif;
  margin-top: 18px;
  font-size: 1.1rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  padding: 12px 32px;
  border-radius: 8px;
  border: none;
  background: linear-gradient(90deg, #4fd1ff, #7cff6b);
  color: #05060c;
  cursor: pointer;
}

.primary-button:hover { filter: brightness(1.1); }
.primary-button:disabled { opacity: 0.6; cursor: default; }

.mode-buttons {
  display: flex;
  gap: 12px;
  margin-top: 18px;
}
.mode-buttons .primary-button { margin-top: 0; }

#play-mp-button { background: linear-gradient(90deg, #ff9b6b, #ff6bd6); }

/* Botón secundario (Controls): discreto, no compite con los de jugar. */
.secondary-button {
  font-family: 'BBA Titles', 'Segoe UI', system-ui, sans-serif;
  margin-top: 12px;
  font-size: 0.85rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  padding: 8px 20px;
  border-radius: 8px;
  border: 1px solid #2a2f4a;
  background: rgba(8, 10, 24, 0.72);
  color: #9aa3c7;
  cursor: pointer;
}

.secondary-button:hover { border-color: #4fd1ff; color: #d9deff; }

/* Overlay de controles: lámina centrada sobre fondo oscurecido; cierra con clic
   fuera de la imagen, la ✕ o Esc (ver menu.js).

   ESTE ES DE LOS TRES QUE SÍ MIDEN EN UNIDADES DE VENTANA, y a propósito. La regla dura de
   `.screen` prohíbe consultar la ventana DENTRO del escenario; este overlay no está adentro:
   es `position: fixed` y cubre la pantalla entera, sin escalar. Para él la ventana ES su caja,
   así que `92vw` y `88vh` son la unidad correcta y al girar el teléfono se reacomoda solo, que
   es lo que tiene que hacer un visor de lámina a pantalla completa.
   Los otros dos con la misma justificación son `.result-toasts` y `.confetti-layer`. */
.controls-overlay {
  position: fixed;
  inset: 0;
  z-index: 40;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(3, 4, 10, 0.85);
}

/* `align-items: center` es lo que hace que la caja mida lo que mide su contenido en vez de
   estirarse: sin eso la fila de pestañas ocupaba los 1100 px del máximo aunque el panel
   táctil midiera 560, y las dos pestañas quedaban flotando lejos de lo que rotulan. */
.controls-box {
  position: relative;
  max-width: min(92vw, 1100px);
  max-height: 88vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
}

/* Las pestañas miden lo suyo y van centradas. Al no llegar al borde derecho de la caja
   dejan libre la esquina donde vive la ✕, que está en posición absoluta. */
.controls-tabs {
  flex: none;
  justify-content: center;
  max-width: calc(100% - 52px);
}
.controls-tabs .panel-tab {
  flex: 0 1 auto;
  min-width: 116px;
}

.controls-pane {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  justify-content: center;
}
.controls-pane.hidden { display: none; }

.controls-image {
  display: block;
  max-width: 100%;
  max-height: 78vh;
  object-fit: contain;
  border: 1px solid #2a2f4a;
  border-radius: 10px;
  box-shadow: 0 12px 48px rgba(0, 0, 0, 0.7);
}


.controls-close {
  position: absolute;
  top: 8px;
  right: 8px;
  width: 34px;
  height: 34px;
  border-radius: 8px;
  border: 1px solid #2a2f4a;
  background: rgba(8, 10, 24, 0.85);
  color: #d9deff;
  font-size: 1rem;
  cursor: pointer;
}

.controls-close:hover { border-color: #4fd1ff; }

/* ---- Selector de país -------------------------------------------------------------------
   Panel con las 197 banderas y un deslizador. Vive DENTRO del escenario (no como el pad
   táctil, que está fuera) porque es una pantalla de menú y tiene que quedar dentro del
   marco. Ver client/src/countryPicker.js.

   La lista NO usa scroll: es un contenido más alto que su ventana, movido con `transform`.
   Así no aparece ninguna barra del navegador, que es la regla del escenario (GDD §4.7). */
.cpick {
  position: absolute;
  inset: 0;
  z-index: 30;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(3, 4, 10, 0.82);

  /* MÍNIMO DE CONTACTO. 44 px REALES en pantalla, que es el mínimo táctil de iOS (Android
     pide 48 dp). Como el escenario está escalado, una medida de diseño NO es una medida
     real: a factor 0.68 una fila de 42 px aterriza en 28. Dividir por la escala convierte
     "44 px reales" en las unidades de diseño que hagan falta.

     NO se condiciona a que el dispositivo sea táctil, y eso fue un error que costó una
     corrida: `body.bba-tactil` es el MODO DE ENTRADA DE LA PARTIDA —una preferencia que el
     jugador guarda—, no una medida de pantalla. Alguien que juega con teclado en un
     portátil táctil igual toca el menú con el dedo, y el menú se usa antes de que exista
     ninguna partida. La regla correcta no habla de táctil: una fila mide 44 px reales
     siempre. En un escritorio eso ya se cumple de sobra y el `max()` no hace nada. */
  --cpick-toque: calc(44px / var(--stage-scale));
  --cpick-fila: max(42px, var(--cpick-toque));
}

.cpick-box {
  width: min(460px, 84%);
  max-height: 86%;
  display: flex;
  flex-direction: column;
  background: rgba(5, 7, 18, 0.92);
  background-clip: padding-box;
  border: 13px solid transparent;
  border-image: url('/assets/sprites/ui/panel.png') 24 fill / 13px / 0 stretch;
  outline: none;
}

.cpick-head {
  display: flex;
  align-items: center;
  gap: 8px;
  padding-bottom: 8px;
}
.cpick-title {
  color: #8b93bb;
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  flex: none;
}
.cpick-search { flex: 1 1 auto; min-width: 0; }
.cpick-close {
  flex: none;
  width: 30px;
  height: 30px;
  border: 1px solid #2a2f4a;
  border-radius: 6px;
  background: rgba(8, 10, 24, 0.85);
  color: #d9deff;
  cursor: pointer;
}
.cpick-close:hover { border-color: #4fd1ff; }

.cpick-body {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  gap: 8px;
}

/* La ventana por la que se ve la lista. `overflow: hidden` y NO `auto`: lo que asoma se
   recorta, no se desplaza. */
.cpick-view {
  position: relative;
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  touch-action: none;   /* el arrastre lo maneja el panel; sin esto el navegador lo roba */
}
.cpick-list { display: flex; flex-direction: column; gap: 4px; will-change: transform; }

.cpick-row {
  display: flex;
  align-items: center;
  gap: 10px;
  flex: none;
  height: var(--cpick-fila);
  padding: 0 10px;
  background: rgba(4, 6, 16, 0.5);
  background-clip: padding-box;
  border: 9px solid transparent;
  border-image: url('/assets/sprites/ui/row.png') 15 fill / 9px / 2px stretch;
  color: #d9deff;
  font-size: 0.92rem;
  text-align: left;
  cursor: pointer;
}
.cpick-row.on {
  border-image-source: url('/assets/sprites/ui/row-selected.png');
  color: #fff;
}
.cpick-flag {
  flex: none;
  width: 33px;
  height: 22px;
  border-radius: 2px;
}
/* El hueco del icono tiene ancho FIJO y existe aunque el icono llegue tarde (la silueta de
   un territorio se descarga). Sin reservarle el sitio, los nombres bailan de izquierda a
   derecha a medida que van apareciendo las figuras. */
.cpick-icon {
  flex: none;
  width: 33px;
  height: 24px;
  display: flex;
  align-items: center;
  justify-content: center;
}

.cpick-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.cpick-empty {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #6a72a0;
  font-size: 0.9rem;
}

/* El carril y el tirador salen de la lámina 09 (barra vertical de 09.2, tirador redondo de
   09.3). El carril se corta en 9-slice SOLO arriba y abajo: los extremos conservan su
   dibujo y el tramo central estira. */
.cpick-track {
  position: relative;
  flex: none;
  width: 20px;
  border-style: solid;
  border-width: 12px 0;
  border-color: transparent;
  border-image: url('/assets/sprites/ui/slider-track.png') 12 0 fill / 12px 0 / 0 stretch;
  touch-action: none;
  cursor: pointer;
}
/* Sin nada que desplazar, el carril se APAGA de verdad. Antes era solo `opacity: 0.35`, y
   con eso el deslizador seguía leyéndose como un control vivo: la pieza de la lámina 09 es
   ámbar saturada y a 35% conserva su color, así que un carril "apagado" y uno vivo se
   distinguían por brillo — la diferencia que peor se ve en un teléfono al sol, y la única
   que un daltónico no tiene.
   Ahora se le quita también el COLOR (`grayscale`) y el contraste, que es un eje
   independiente del brillo: queda gris plano contra ámbar. El tirador se apaga un punto más
   que su carril porque es la parte que invita a agarrar. */
.cpick-track-off {
  opacity: 0.22;
  filter: grayscale(1) contrast(0.65);
  pointer-events: none;
}
.cpick-track-off .cpick-handle { opacity: 0.55; }

/* El dibujo del tirador mide 26 px y no se toca: agrandarlo lo convertiría en otra cosa.
   Lo que crece es su CAJA, hasta el mínimo de contacto, con el círculo centrado dentro.
   Así se agarra con el dedo sin cambiar lo que se ve. */
.cpick-handle {
  position: absolute;
  left: 50%;
  top: 0;
  width: 34px;
  height: max(26px, var(--cpick-toque));
  margin-left: -17px;
  background: url('/assets/sprites/ui/slider-handle.png') center / 26px 26px no-repeat;
  cursor: grab;
  touch-action: none;
  outline: none;
}
.cpick-handle:focus-visible { filter: drop-shadow(0 0 4px #4fd1ff); }
.cpick-handle.arrastrando { cursor: grabbing; filter: brightness(1.25) drop-shadow(0 0 5px #ffd76a); }

.cpick-foot { padding-top: 8px; display: flex; justify-content: flex-end; }
.cpick-foot .secondary-button { margin: 0; }

/* (Acá había un `@media (max-height: 470px)` que ensanchaba la caja en «teléfono apaisado».
   Su premisa era falsa: el escenario mide 576 px de alto SIEMPRE, en un teléfono y en un
   monitor — lo que cambia es el zoom. Lo que hacía de verdad era darle dos tamaños distintos
   al mismo panel según la orientación del aparato, así que al girar el teléfono la caja
   cambiaba de ancho sola. Ver la regla dura en `.screen`. */

.searching-panel {
  margin-top: 18px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
}

.searching-text {
  color: #d9deff;
  font-size: 1rem;
}

.searching-panel .primary-button { margin-top: 0; }

/* ---- Cuentas y resultado, también apaisados ------------------------------------------
   Mismo criterio que el menú: en un escenario 16:9 apilar el logo (o la imagen de
   victoria) encima de un formulario o de una tabla no entra, y desplazar no es una
   opción. Poniéndolos al costado se usa el ancho que sobra y todo cabe. */
#auth-screen,
#result-screen {
  display: grid;
  /* La primera columna lleva ancho FIJO, no `minmax(0, …)`: el logo está posicionado en
     absoluto y ya no ocupa celda, así que con minmax la columna resolvería a cero y el
     formulario se correría hacia la izquierda. */
  grid-template-columns: var(--col-izq) minmax(0, var(--col-der));
  align-items: center;
  justify-content: center;
  gap: 10px 24px;
}

/* ---- El logo NO se acomoda: está anclado ------------------------------------------
   Estuvo dentro de la rejilla, y ahí se centraba respecto de filas cuya altura depende
   del panel abierto — así que cada panel (inicio, multijugador, práctica, cómo jugar) y
   la pantalla de cuentas lo dejaban en un sitio distinto. Con la misma imagen en todas,
   ese salto se nota mucho.
   Ahora está posicionado en absoluto respecto de la pantalla, que siempre mide lo mismo:
   centrado verticalmente y centrado en la columna izquierda. Como el desplazamiento se
   calcula a partir de las variables de columna, no hay ningún número mágico y el logo
   sigue a la maqueta si esta cambia. Al no ocupar celda, tampoco puede estirar una fila
   ni empujar nada.
   Sigue desapareciendo donde no hace falta: ver `.menu-shell.wide .menu-logo`. */
#auth-screen .menu-logo,
.menu-shell .menu-logo {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%)
             translateX(calc((var(--col-der) + var(--col-gap)) / -2));
  margin: 0;
  z-index: 2;
}
#auth-screen .auth-box { grid-column: 2; width: 100%; }

#result-screen .result-image { grid-column: 1; grid-row: 1 / span 8; justify-self: center; }
#result-screen > :not(.result-image) { grid-column: 2; width: 100%; justify-self: center; }

/* La segunda pantalla de fin de partida conserva la ESCENA de la primera —cerebro a la
   izquierda, contenido a la derecha— porque es la misma escena en dos tiempos: si la
   disposición cambiara, el paso se leería como un salto a otro sitio del juego.
   Pero NO comparte su rejilla, y ese fue un defecto real: `#result-screen` tiene ocho hijos
   chicos, uno por fila, y su cerebro ocupa `grid-row: 1 / span 8`. Al reusar esa regla con
   solo dos hijos, el cuerpo caía en una fila de 244 px, desbordaba centrado hacia arriba y
   hacia abajo, y el botón —fila 2— le quedaba encima. Acá las filas se declaran: una que se
   estira y toma lo que sobra, y otra del alto del botón. */
/* LAS DOS FILAS SON `auto`, NO `1fr auto`. Con `1fr` la primera se quedaba con todo el alto
   sobrante y empujaba el botón contra el borde de abajo, dejando un hueco entre el panel y
   él. Con las dos ceñidas a su contenido y `align-content: center`, el par panel+botón se
   centra como un bloque y el botón queda pegado a su panel. */
#rank-screen {
  display: grid;
  grid-template-columns: var(--col-izq) minmax(0, var(--col-der));
  grid-template-rows: auto auto;
  align-items: center;
  justify-content: center;
  align-content: center;
  gap: 10px 24px;
}
#rank-screen .result-image { grid-column: 1; grid-row: 1 / span 2; justify-self: center; }
/* `align-self: stretch` es lo que le da a la caja el alto de su fila —y por lo tanto algo
   contra qué desbordar—; `min-height: 0` es lo que le permite encogerse por debajo de su
   contenido, que es la condición para que ese desbordamiento exista y se pueda medir. */
/* CEÑIDO A SU CONTENIDO, no estirado a la fila. Estirado, el panel medía todo el alto
   disponible y el contenido llenaba dos tercios: quedaba un vacío enorme abajo que se leía
   como que algo faltaba. `max-height: 100%` mantiene el tope —nada se desplaza nunca— pero
   el alto real lo pone lo que hay adentro. */
/* El tope se mide contra el ESCENARIO y no con `100%` ni `vh`. Con la fila ya en `auto`, un
   `100%` se resolvería contra la altura del propio contenido y no limitaría nada; y `vh` es
   la ventana, que no describe este escenario de tamaño fijo que se escala en bloque. Los
   120 px que se descuentan son el botón, su hueco y aire para que el panel no toque el marco. */
#rank-screen .rp-body {
  grid-column: 2;
  grid-row: 1;
  align-self: center;
  max-height: calc(var(--stage-h) - 120px);
  min-height: 0;
}
#rank-screen #rank-back { grid-column: 2; grid-row: 2; width: 100%; justify-self: center; }

/* El cuerpo tiene alto ACOTADO y es quien decide si el Top 11 entra: `rankProgress.js` mide
   su desbordamiento después de dibujar y quita la segunda tabla si no cabe. Sin un alto
   máximo no habría desbordamiento que medir y las dos tablas empujarían el botón fuera del
   escenario — el defecto que los paneles del menú ya tienen resuelto. */
/* El alto lo fija la fila de la rejilla, no un `max-height` en vh: la pantalla es un
   escenario de tamaño fijo que se escala en bloque, así que "66% del alto de la ventana" no
   describe nada de lo que hay acá adentro.

   EL MARCO ES EL MISMO PANEL DEL MENÚ (lámina 10), con su mismo recorte y su mismo respaldo.
   No se dibujó uno nuevo a propósito: esta pantalla es parte del juego, no un cartel aparte,
   y la única forma de que se lea así es que use las piezas que el jugador ya vio en los
   nueve paneles del menú. `background-clip: padding-box` va porque sin él el respaldo asoma
   en cuadrado por fuera de los ángulos cortados del marco. */
.rp-body {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 2px;
  overflow: hidden;
  padding: 6px 10px 8px;
  background: rgba(5, 7, 18, 0.55);
  background-clip: padding-box;
  border: 13px solid transparent;
  border-image: url('/assets/sprites/ui/panel.png') 24 fill / 13px / 0 stretch;
}

.rp-title { margin: 0; font-size: 1.3rem; line-height: 1.1; }
/* El estado solo habla mientras se espera o si la partida no puntuó. `.panel-status` reserva
   1,2em de alto para no dar saltos cuando aparece un mensaje; acá esa reserva es un hueco
   permanente al pie del panel, y el panel ya no da saltos porque está ceñido a su contenido. */
#rank-screen #rp-status { min-height: 0; }
#rank-screen #rp-status:empty { display: none; }
.rp-place { margin: 0; color: #d9deff; font-weight: 700; }
.rp-place .rr-of { color: #6a72a0; font-weight: 400; font-size: 0.82rem; }

/* Los dos números que el jugador vino a ver. Cifras tabulares: al animarse cuenta a cuenta,
   sin ancho fijo por dígito el número tiembla y el movimiento se lee como un defecto. */
/* Las dos cifras van en TARJETAS, con la pieza de fila de la lámina 04 — la misma que
   visten los logros, los ítems de tienda y la tarjeta de identidad. Es lo que las convierte
   en el objeto que el ojo busca primero al entrar, que es exactamente su trabajo: son el
   dato que el jugador vino a ver.
   Ancho mínimo igual en las dos para que el par se lea como una unidad y no como dos cosas
   de tamaños distintos que quedaron al lado. */
.rp-figures { display: flex; gap: 12px; margin: 4px 0 2px; justify-content: center; }
.rp-cell {
  display: flex;
  flex-direction: column;
  align-items: center;
  line-height: 1.15;
  min-width: 122px;
  padding: 5px 10px;
  background: rgba(4, 6, 16, 0.5);
  background-clip: padding-box;
  border: 9px solid transparent;
  border-image: url('/assets/sprites/ui/row.png') 15 fill / 9px / 2px stretch;
}
.rp-label { font-size: 0.62rem; color: #6a72a0; text-transform: uppercase; letter-spacing: 0.09em; }
.rp-value { font-size: 1.9rem; font-weight: 800; color: #f0f2ff; font-variant-numeric: tabular-nums; }
.rp-move { font-size: 0.9rem; font-weight: 800; font-variant-numeric: tabular-nums; }
/* Verde sube, rojo baja, azul no se movió. Tres estados y tres colores, pero el SIGNO va
   además en la flecha: quien no distingue rojo de verde lee ▲ y ▼ igual de bien. */
.rp-up { color: #7cff6b; }
.rp-down { color: #ff6b6b; }
.rp-flat { color: #4fd1ff; }

.rp-headline { margin: 2px 0 0; font-weight: 800; font-size: 1rem; }

/* Las dos tablas, la de vecinos y (si entra) el Top 11. `min-height: 0` es lo que permite
   que este bloque se encoja dentro del flex y por lo tanto que llegue a desbordar: sin él
   un hijo flex nunca baja de su tamaño de contenido y la medición no detectaría nada. */
/* APILADAS, no lado a lado: el Top 11 va DEBAJO de la ventana de vecinos. La diferencia no
   es estética — las dos tablas tienen la misma forma (puesto, nombre, puntos) y puestas en
   paralelo el ojo las compara fila contra fila, que es exactamente lo que no significan.
   Una debajo de la otra se leen como lo que son: "dónde estás" y, aparte, "quiénes mandan". */
/* LAS DOS TABLAS EN COLUMNAS, separadas por una regla.
   Apiladas no entraban —dos veces once filas piden el doble de alto del que hay— y quitar
   una era perder la mitad de la información. En paralelo entran las dos y además el panel
   deja de ser una tira angosta con aire a los costados: usa el ancho que ya tenía.
   La regla vertical hace el trabajo que antes hacía la distancia: dice que son DOS cosas,
   no una lista partida. Sin ella el ojo lee las veintidós filas como una sola tabla. */
.rp-boards {
  display: flex;
  flex-direction: row;
  align-items: stretch;
  gap: 0;
  flex: 0 1 auto;
  min-height: 0;
  width: 100%;
  overflow: hidden;
  border-top: 1px solid rgba(120, 132, 180, 0.28);
  padding-top: 5px;
  margin-top: 2px;
}
.rp-board { flex: 1 1 0; min-width: 0; padding: 0 7px; }
.rp-board-top { display: flex; flex: 1 1 0; min-width: 0; border-left: 1px solid rgba(120, 132, 180, 0.28); }

/* `.rr-board` nació con 158 px fijos para el bloque angosto que vivía DENTRO de la pantalla
   de resultado. Acá cada tabla tiene su columna, así que se suelta ese ancho. */
.rp-board .rr-board { width: 100%; font-size: 0.7rem; gap: 0; }
/* Separador por fila. Una regla finísima —no un fondo alternado— porque son once filas de
   números alineados: lo que hace falta es guiar el ojo a lo largo del renglón, no partir la
   tabla en bandas. La última no lleva, o se dibujaría un borde suelto contra el aire. */
.rp-board .rr-board li { border-bottom: 1px solid rgba(120, 132, 180, 0.12); padding: 1px 3px; }
.rp-board .rr-board li:last-child { border-bottom: none; }
/* TU fila, con la pieza de "seleccionado" (lámina 04) — la misma que marca la pestaña
   activa, el ítem equipado y el logro conseguido. El jugador ya aprendió que ese marco
   significa "este eres tú / esto es lo tuyo", así que acá no hay nada nuevo que entender.
   Reemplaza al tinte celeste plano que tenía: un fondo apenas más claro se pierde entre
   once filas, y esta pantalla existe para que encuentres la tuya de un vistazo. */
#rank-screen .rr-board li.self {
  background: none;
  /* 6 px y no 7: en dos columnas cada tabla es más angosta y el marco se comía el nombre.
     `border-bottom` se declara después del separador de fila para que el marco gane —si no,
     la regla de una sola línea le pisaría el borde de abajo a la pieza. */
  border: 6px solid transparent;
  border-image: url('/assets/sprites/ui/row-selected.png') 15 fill / 6px / 2px stretch;
  border-radius: 0;
  padding: 0 3px;
}
#rank-screen .rr-board li.self .rank-pos { color: #d9deff; }
.rp-board-title {
  margin: 0 0 3px;
  font-size: 0.62rem;
  color: #6a72a0;
  text-transform: uppercase;
  letter-spacing: 0.09em;
}
/* El envoltorio del Top 11 existe para que la medición pueda quitarlo de una pieza: es lo
   ÚNICO que se sacrifica si las dos tablas no entran (ver ajustarTablas). */
.rp-board-top { min-width: 0; }

/* (Acá había dos `@media (max-aspect-ratio: 1 / 1)` que apilaban en una columna la pantalla de
   rankings y las de cuentas y resultado «cuando la ventana es más alta que ancha». No puede
   ser: el escenario es 16:9 en las dos orientaciones y esa disposición apilada no existe. Lo
   que producían era una pantalla reordenada a medias al girar el teléfono. Ver `.screen`. */

/* El margen es chico a propósito: con 14px el contenido del menú principal medía 559px
   en una caja de 548 y se comía el relleno del marco. Ahí todavía se veía todo, pero
   bastaba una entrada más en el menú para empezar a cortar. */
#menu-status {
  margin-top: 4px;
  min-height: 1.4em;
  color: #9aa3c7;
}

/* ---- Pantalla inicial de autenticación (fuera del ciclo de juego) ---- */
.auth-box {
  margin-top: 14px;
  width: 360px;   /* ídem: 90vw solo mordía al girar el teléfono */
  border: 1px solid #2a2f4a;
  border-radius: 12px;
  background: rgba(8, 10, 24, 0.72);
  padding: 16px 20px 14px;
  display: flex;
  flex-direction: column;
  align-items: stretch;
}

.auth-tabs { display: flex; gap: 6px; margin-bottom: 14px; }

.auth-tab {
  flex: 1;
  padding: 8px 4px;
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  border-radius: 8px;
  border: 1px solid #2a2f4a;
  background: transparent;
  color: #9aa3c7;
  cursor: pointer;
}

.auth-tab.active { border-color: #4fd1ff; color: #f0f2ff; background: rgba(79, 209, 255, 0.08); }

.auth-panel { display: flex; flex-direction: column; gap: 10px; }

.auth-panel input[type="email"],
.auth-panel input[type="password"],
.auth-panel input:not([type]) {
  font-size: 0.95rem;
  padding: 10px 12px;
  border-radius: 8px;
  border: 1px solid #2a2f4a;
  background: #10132a;
  color: #f0f2ff;
  outline: none;
}

.auth-panel input:focus { border-color: #4fd1ff; }

.auth-checks { display: flex; flex-direction: column; gap: 4px; }

.auth-check {
  font-size: 0.8rem;
  color: #9aa3c7;
  display: flex;
  align-items: center;
  gap: 7px;
  cursor: pointer;
}

.auth-submit { margin-top: 4px; }

.auth-hint { font-size: 0.72rem; color: #6a72a0; margin: 0; }

.auth-status { min-height: 1.2em; margin: 10px 0 0; font-size: 0.8rem; color: #9aa3c7; text-align: center; }
.auth-status.auth-error { color: #ff6b6b; }

.auth-divider {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 12px 0 10px;
  color: #6a72a0;
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
}
.auth-divider::before, .auth-divider::after { content: ''; flex: 1; height: 1px; background: #2a2f4a; }

.auth-providers { display: flex; gap: 8px; margin-bottom: 8px; }

.provider-button {
  flex: 1;
  padding: 9px 4px;
  font-size: 0.78rem;
  font-weight: 700;
  border-radius: 8px;
  border: 1px solid #2a2f4a;
  background: rgba(16, 19, 42, 0.7);
  color: #6a72a0;
  cursor: not-allowed;
  opacity: 0.65;
}

/* ---- Identidad en el menú (viene de la pantalla de auth) ---- */
.identity-bar {
  margin-top: 14px;
  display: flex;
  align-items: center;
  gap: 12px;
  border: 1px solid #2a2f4a;
  border-radius: 8px;
  background: rgba(8, 10, 24, 0.72);
  padding: 9px 16px;
}

#identity-name { color: #f0f2ff; font-weight: 700; letter-spacing: 0.04em; }

.link-button {
  border: none;
  background: transparent;
  color: #6a72a0;
  font-size: 0.75rem;
  text-decoration: underline;
  cursor: pointer;
}
.link-button:hover { color: #4fd1ff; }

/* ---- Botón de pantalla completa ---------------------------------------------------------
   Vive FUERA de las pantallas, fijo contra la esquina superior izquierda, porque la acción
   vale igual en el menú, en la tienda y en partida — meterlo en una barra lo habría atado a
   una sola. Y arriba a la izquierda por una razón física: en un teléfono en horizontal, ese
   rincón es la única zona que ningún pulgar tapa mientras se juega.

   El ámbar no es decoración: la interfaz entera es cian y magenta, así que un dorado no
   compite con nada y se encuentra sin buscarlo. El icono sale de la lámina de dirección de
   arte (ver devtools/art/icono-fullscreen.js), no es un glifo de texto: los emoji de pantalla
   completa se dibujan distinto en cada sistema y en algunos ni existen.

   El latido se detiene al primer uso —`aria-pressed="true"`— y también si el sistema pide
   menos animación. Un botón que sigue pidiendo atención después de que le hiciste caso pasa
   de llamativo a molesto. */
.fs-btn {
  position: fixed;
  top: 10px;
  left: 10px;
  z-index: 60;
  width: 46px;
  height: 46px;
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  border: 2px solid #ffc430;
  border-radius: 10px;
  background: linear-gradient(160deg, rgba(58, 40, 4, 0.92), rgba(16, 12, 4, 0.92));
  box-shadow: 0 0 10px rgba(255, 196, 48, 0.45), inset 0 0 8px rgba(255, 196, 48, 0.18);
  animation: fsLatido 2.6s ease-in-out infinite;
}
.fs-btn:hover,
.fs-btn:focus-visible {
  border-color: #fff2b0;
  box-shadow: 0 0 16px rgba(255, 220, 120, 0.75), inset 0 0 10px rgba(255, 220, 120, 0.28);
  outline: none;
}
.fs-btn:active { transform: scale(0.94); }
.fs-btn[aria-pressed="true"] { animation: none; }

/* LA FIGURA, REHECHA (dueño, 2026-08-15: *"la figura central es poco clara y está un poco sucia
   en cuanto a la definición"*). El borde, el ámbar, el resplandor y el latido no se tocaron —eso
   es lo que dijo que estaba bien—; lo que cambió es solo lo de adentro.

   Tres cosas se iban juntas y las tres eran del asset viejo, que se RECORTABA de una lámina
   compuesta sobre negro (ver devtools/art/icono-fullscreen.js):

   · medía 50x53, así que `image-rendering: pixelated` estaba puesto para que no se empastara al
     ampliarlo. El icono nuevo es vectorial, se genera a 160 y acá se REDUCE: con `pixelated`,
     reducir cinco veces tira píxeles enteros y deja el trazo mordido. Por eso se va.
   · arrastraba el fondo del panel de la lámina, o sea un lavado marrón sobre todo el cuadrado.
   · no tenía flecha, y el "salir" se resolvía girando el icono 180°. Con la figura nueva ese
     giro se lleva la flecha a la esquina de abajo a la derecha y las cuatro esquinas pierden su
     ancla común; la lámina del dueño hace lo correcto —la flecha se queda arriba a la izquierda
     y lo único que se invierte es hacia dónde apunta—, así que ahora son dos piezas.

   30 px y no 26: es la proporción de la lámina (la figura ocupa dos tercios del botón, no la
   mitad larga), y es parte de lo que se pidió — a 26 la figura queda chica dentro de su marco. */
.fs-icono {
  width: 30px;
  height: 30px;
  background: url('assets/sprites/ui/icon-fullscreen.png') center/contain no-repeat;
}
/* Dentro de pantalla completa, la flecha apunta hacia ADENTRO: la misma esquina, el sentido
   contrario. Se lee "salir" sin un rótulo y sin mover el resto de la figura. */
.fs-btn[aria-pressed="true"] .fs-icono {
  background-image: url('assets/sprites/ui/icon-fullscreen-exit.png');
}

@keyframes fsLatido {
  0%, 100% { box-shadow: 0 0 10px rgba(255, 196, 48, 0.40), inset 0 0 8px rgba(255, 196, 48, 0.16); }
  50%      { box-shadow: 0 0 20px rgba(255, 210, 90, 0.85), inset 0 0 12px rgba(255, 210, 90, 0.30); }
}
@media (prefers-reduced-motion: reduce) {
  .fs-btn { animation: none; }
}

.menu-tables {
  margin-top: 22px;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 14px;
}

.highscore-panel {
  width: 300px;
  border: 1px solid #2a2f4a;
  border-radius: 10px;
  background: rgba(8, 10, 24, 0.72);
  padding: 12px 18px;
}

.highscore-panel h3 {
  margin: 0 0 8px;
  font-size: 0.85rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: #9aa3c7;
  text-align: center;
}

/* ---- RANKINGS TERRITORIALES ------------------------------------------------------------
   Once filas y ni una más, por diseño: el servidor no ofrece paginación libre (ver
   docs/RANKINGS.md §7) y por lo tanto acá tampoco hace falta paginador. La tabla entra
   entera y nada se desplaza. */

.rank-crumbs {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  gap: 2px 4px;
  font-size: 0.72rem;
  min-height: 18px;
}

.rank-crumb {
  background: none;
  border: none;
  padding: 1px 4px;
  border-radius: 4px;
  color: #7f89b8;
  font: inherit;
  cursor: pointer;
}

.rank-crumb:hover { color: #d9deff; background: rgba(79, 209, 255, 0.12); }
.rank-crumb.current { color: #4fd1ff; cursor: default; }
.rank-crumb.current:hover { background: none; }
.rank-crumb-sep { color: #3b4266; }

/* Baja un nivel: abre el panel con corredera (el mismo del selector de país). Va en la
   misma línea que el breadcrumb porque el alto del panel está ajustado al píxel — una fila
   propia costaría dos filas de tabla. */
.rank-descend {
  font: inherit;
  font-size: 0.7rem;
  background: rgba(8, 10, 24, 0.72);
  color: #7f89b8;
  border: 1px solid #2a2f4a;
  border-radius: 5px;
  padding: 1px 7px;
  cursor: pointer;
}

.rank-descend:hover { color: #4fd1ff; border-color: #4fd1ff; }

/* `flex: 0 0 auto`, y el detalle no es cosmético: con `1 1 auto` la fila se ENCOGÍA por
   debajo de su contenido cuando el panel apretaba, y como la columna del territorio se
   centra, lo que sobraba se desbordaba hacia arriba — el lienzo del mapa terminaba encima
   del breadcrumb y se comía los clics al botón "World". Pasaba solo en nivel comuna, donde
   el título ocupa dos líneas y la columna crece: de ahí el "a veces no funciona". */
.rank-body {
  display: flex;
  gap: 16px;
  align-items: stretch;
  justify-content: center;
  flex: 0 0 auto;
}

/* `justify-content: center` porque la columna es más baja que la tabla de once filas: sin
   él el mapa se pega arriba y queda descolgado del bloque que acompaña. */
.rank-territory {
  width: 236px;
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  text-align: center;
}

/* Alto fijo: la silueta llega asíncrona y sin reservarle el sitio, la tabla salta cuando
   termina de bajar.

   Y el recuadro es VERTICAL, no apaisado, y eso se midió: la silueta se ajusta por su lado
   largo, así que un territorio muy alto y angosto queda gobernado por el alto de la caja.
   Con 132×104, Chile —el país más vertical del mundo— se dibujaba con 5 píxeles de ancho:
   el lienzo estaba, la figura estaba, y no se veía nada. A los anchos (el mapamundi, que
   mide 2,4:1) subir el alto no les quita nada, porque a ellos los gobierna el ancho. */
.rank-shape {
  width: 224px;
  height: 258px;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* Cuando lo que se dibuja es el territorio PADRE —porque el propio es un archipiélago
   disperso o la fuente lo publica como un rectángulo— va atenuado: así se lee como
   contexto y no como "así es tu comuna". */
.rank-shape-padre canvas { opacity: 0.42; }

.rank-territory-name {
  margin: 0;
  font-size: 0.82rem;
  color: #d9deff;
  line-height: 1.15;
}

.rank-holder {
  margin: 0;
  font-size: 0.8rem;
  color: #4fd1ff;
  line-height: 1.15;
}

.rank-title-line {
  margin: 0;
  font-size: 0.7rem;
  color: #ffd76b;
  line-height: 1.2;
}

.rank-members {
  margin: 0;
  font-size: 0.66rem;
  color: #6a72a0;
  font-variant-numeric: tabular-nums;
}

.rank-main {
  flex: 1 1 0;
  min-width: 0;
  /* Ancho medido con la base poblada: entra "Nombre  High Score Master (Interim) of Región
     Metropolitana  56611" sin recortar. El panel es ancho y el espacio estaba sin usar. */
  max-width: 660px;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.rank-views {
  display: flex;
  gap: 6px;
  justify-content: center;
}

.rank-view {
  background: rgba(8, 10, 24, 0.72);
  border: 1px solid #2a2f4a;
  border-radius: 6px;
  padding: 2px 10px;
  color: #7f89b8;
  font: inherit;
  font-size: 0.7rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  cursor: pointer;
}

.rank-view.active { color: #4fd1ff; border-color: #4fd1ff; }
.rank-view[disabled] { opacity: 0.4; cursor: default; }

.rank-board {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 3px;
}

.rank-board li {
  display: flex;
  align-items: center;
  gap: 7px;
  font-size: 0.8rem;
  padding: 1px 6px;
  border-radius: 5px;
  min-height: 19px;
}

/* La fila propia se marca: en una ventana de once alrededor tuyo, encontrarte es lo
   primero que hacés. */
.rank-board li.self { background: rgba(79, 209, 255, 0.14); }
.rank-board li.champion .rank-pos { color: #ffd76b; }

.rank-pos {
  color: #6a72a0;
  width: 2.4em;
  text-align: right;
  font-variant-numeric: tabular-nums;
  flex: 0 0 auto;
}

.rank-who {
  flex: 1 1 auto;
  min-width: 0;
  text-align: left;
  color: #d9deff;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.rank-geo-title {
  color: #ffd76b;
  font-size: 0.92em;
}

.rank-points {
  color: #7fd4a0;
  font-variant-numeric: tabular-nums;
  flex: 0 0 auto;
}

.rank-empty {
  color: #6a72a0;
  font-size: 0.8rem;
  text-align: center;
  padding: 8px 0;
}

/* ---- Avance territorial en la pantalla de fin de partida ------------------------------
   Silueta + puesto a la izquierda, la tabla del barrio a la derecha. Aparece unos segundos
   después del resultado (el puntaje se acredita cuando ambos jugadores reportaron), así
   que se le reserva el sitio con `min-height` para que nada salte al llegar. */
.result-rank {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 14px;
  margin: 6px auto 0;
  padding: 8px 14px;
  /* Medido, no elegido: con menos ancho el renglón "#1 ▲3 +1200 pts" se parte en dos y
     deja de leerse como una cifra. Lo comprueba shot-rank-postpartida.js. */
  max-width: 668px;
  border: 1px solid #2a2f4a;
  border-radius: 10px;
  background: rgba(8, 10, 24, 0.72);
  animation: rr-entra 380ms ease-out;
}

@keyframes rr-entra {
  from { opacity: 0; transform: translateY(6px); }
  to { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  .result-rank { animation: none; }
}

/* Vertical por el mismo motivo que `.rank-shape`: con la caja apaisada, Chile se dibujaba
   con cuatro píxeles de ancho. */
.rr-shape { width: 84px; height: 112px; flex: 0 0 auto; display: flex; align-items: center; justify-content: center; }

/* `flex: 1 1 0` con `min-width: 0`, y las dos mitades importan. Con `flex: 0 0 auto` la
   columna se dimensionaba por su texto más largo —"You are now Neighborhood Boss of
   Ñuñoa!" no cabe en 560 px— y empujaba la tabla FUERA del recuadro, dejándola sin ancho
   para los nombres. Se veían los puestos y los puntos, y ningún jugador. */
.rr-info { flex: 1 1 0; min-width: 0; text-align: left; }
.rr-place { margin: 0; font-size: 0.86rem; color: #d9deff; }

.rr-headline { margin: 2px 0; font-size: 0.76rem; overflow-wrap: anywhere; }
.rr-title { color: #ffd76b; }
.rr-first { color: #4fd1ff; }
.rr-top { color: #7fd4a0; }
.rr-lost { color: #ff8f8f; }

/* Los tres datos del renglón —puesto, puestos ganados, puntos— se leen juntos o no se
   leen: partidos en varias líneas ("▲" arriba y "1" abajo) dejan de ser una cifra. */
.rr-numbers { margin: 2px 0 0; font-size: 0.78rem; display: flex; flex-wrap: wrap; gap: 3px 7px; align-items: baseline; font-variant-numeric: tabular-nums; }
.rr-numbers > span { white-space: nowrap; }
.rr-pos { color: #d9deff; font-size: 1.05rem; }
/* `nowrap` para que el corte de línea caiga ANTES del "· 70 players" y no en medio: partido
   queda "· 70" arriba y "players" abajo, que se lee como dos datos distintos. */
.rr-of { color: #6a72a0; font-size: 0.7rem; white-space: nowrap; }
.rr-delta { color: #7fd4a0; }
.rr-up { color: #7fd4a0; }
.rr-down { color: #ff8f8f; }
.rr-flat { color: #6a72a0; }

/* Ancho FIJO: es una columna de once filas con nombre y puntos, y su sitio no puede
   depender de cuán largo sea el titular de al lado. */
.rr-board {
  list-style: none;
  margin: 0;
  padding: 0;
  flex: 0 0 auto;
  width: 158px;
  display: flex;
  flex-direction: column;
  gap: 2px;
  font-size: 0.72rem;
}

.rr-board li { display: flex; gap: 6px; align-items: center; padding: 0 4px; border-radius: 4px; }
.rr-board .rank-pos { width: 1.9em; }
.rr-board li.self { background: rgba(79, 209, 255, 0.16); }

/* Sin `max-height` + `overflow-y: auto`: eso era una barra de desplazamiento, que el
   diseño prohíbe en todas las plataformas. Las dos tablas se reparten en páginas como el
   resto de las listas del menú (ver paginar() en menuPanels.js). */
#highscore-list,
#ranking-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
  flex: 1 1 0;
  min-height: 0;
  overflow: hidden;
}

#highscore-list li,
#ranking-list li {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 0.85rem;
}

.ranking-record {
  color: #7fd4a0;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.highscore-rank {
  color: #6a72a0;
  width: 1.6em;
  text-align: right;
  font-variant-numeric: tabular-nums;
}

.highscore-name {
  flex: 1;
  text-align: left;
  color: #d9deff;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.highscore-score {
  color: #ffe066;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.highscore-empty {
  color: #6a72a0;
  justify-content: center;
}

/* La partida no lleva separación entre HUD, lienzo y línea de controles: su alto es
   exactamente el que define el escenario, y un gap extra la haría desbordar. */
#game-screen { gap: 0; }

/* ---- Presentación VS (antes del primer juego; la sim espera en pausa determinista) ---- */
.vs-overlay {
  position: absolute;
  inset: 0;
  z-index: 30;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 12px;
  background: radial-gradient(ellipse at center, #141939 0%, #05060c 78%);
  overflow: hidden;
}

.vs-name {
  /* El nombre lo escribe el jugador: acá la reserva del sistema no es un adorno. */
  font-family: 'BBA Titles', 'Courier New', monospace;
  font-weight: 900;
  text-transform: uppercase;
  font-size: 3.2rem;   /* era clamp(1.5rem, 6.5vw, 3.2rem): en el escenario 6.5vw pasa el tope siempre */
  max-width: 92%;
  display: flex;
  justify-content: center;
  /* El recorte por puntos suspensivos lo hace ahora el propio nombre dentro del bloque de
     perfil. Acá NO puede haber `overflow: hidden`: la bandera sobresale del avatar a
     propósito y quedaría cortada por la mitad. */
}

/* El cartel entra inclinado (`skewX(-7deg)`) y esa inclinación deforma también la imagen:
   el avatar salía como una elipse. Se le devuelve el mismo ángulo al contrario, así el
   texto conserva el sesgo arcade y el avatar sigue siendo un círculo. */
.vs-name .bba-id-pic { transform: skewX(7deg); }

/* El espaciado dramático es de ESTE cartel, no del bloque de perfil. */
.vs-name .bba-id-name { letter-spacing: 0.12em; }
.vs-name .bba-id-title { letter-spacing: 0.22em; }

/* ==== BLOQUE DE PERFIL ==================================================================
   UNA sola disposición para las cinco vistas que muestran a una persona: la barra del
   menú, la tarjeta del perfil, las filas del panel social, la presentación VS y los dos
   paneles del marcador. El HTML lo arma client/src/identity.js; acá vive la forma.

       ┌──────────┐
       │          │   ☆ Nombre        insignia + nombre en SU color
       │  AVATAR  │   TÍTULO          mismo tono, más oscuro
       │        ▟▛│
       └──────────┘
          la bandera con su CENTRO sobre la esquina inferior derecha del avatar

   Entre vistas cambia SOLO la escala. Todo se deriva de `--id-avatar`, así que una talla
   nueva es una línea con cuatro números y no hay forma de que la bandera quede descolocada
   o el avatar deje de cubrir las dos líneas. */

.bba-id {
  display: flex;
  align-items: center;
  gap: var(--id-gap, 9px);
  min-width: 0;
  text-align: left;
}

/* El avatar manda: es lo más grande del bloque y su alto es el de las dos líneas de texto
   juntas. Los valores de cada talla están calculados así — ver las tallas más abajo. */
.bba-id-pic {
  position: relative;
  flex: none;
  width: var(--id-avatar);
  height: var(--id-avatar);
  /* La bandera está centrada SOBRE el canto del círculo, así que la mitad de su ancho
     sobresale de la caja: (ancho/2) − (1 − 0.8536)·diámetro = 0.109 del diámetro. Se
     reserva acá para que no se meta debajo del texto ni la recorte quien esté al lado. */
  margin-right: calc(var(--id-avatar) * 0.11);
}

.bba-id-avatar {
  display: block;
  width: 100%;
  height: 100%;
  border-radius: 50%;
  object-fit: cover;
  /* Sin avatar el hueco se conserva (la CPU no tiene, las cuentas nuevas tampoco): si el
     bloque se encogiera, las dos mitades del marcador dejarían de alinearse. */
  background: rgba(79, 209, 255, 0.10);
  border: 1px solid #2a2f4a;
}

/* 0.8536 = 50% + 35.36%, que es el punto a 45° de la circunferencia: la "esquina inferior
   derecha" de un avatar redondo. Con `translate(-50%,-50%)` lo que cae ahí es el CENTRO de
   la bandera, que es lo pedido. Si algún día el avatar deja de ser un círculo, este mismo
   85.36% pasa a ser un 100% y nada más cambia. */
.bba-id-flag {
  display: block;
  position: absolute;
  left: 85.36%;
  top: 85.36%;
  transform: translate(-50%, -50%);
  height: calc(var(--id-avatar) * 0.34);
  width: calc(var(--id-avatar) * 0.51);
  border-radius: 2px;
  /* Un canto oscuro la separa del avatar: varias banderas tienen blanco en el borde y sin
     esto se funden con el avatar que tienen debajo. */
  box-shadow: 0 0 0 1px rgba(4, 6, 16, 0.92);
}

.bba-id-text {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
}

.bba-id-line {
  display: flex;
  align-items: baseline;
  gap: 6px;
  min-width: 0;
}

.bba-id-badge {
  flex: none;
  font-size: var(--id-badge);
  line-height: 1;
}

.bba-id-name {
  min-width: 0;
  font-size: var(--id-name);
  line-height: 1.15;
  font-weight: 800;
  letter-spacing: 0.02em;
  /* El color lo escribe identity.js en el propio elemento: es el color de paleta que el
     jugador eligió, o —en partida— el que le tocó de verdad tras resolver los choques. */
  color: var(--id-color, #f0f2ff);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* El título en el mismo tono, un paso más oscuro (el `dark` de la paleta de Skins). La
   línea ocupa su sitio aunque esté vacía: es lo que garantiza que el avatar cubra dos
   renglones también cuando no hay título equipado. */
.bba-id-title {
  min-height: 1.15em;
  font-size: var(--id-title);
  line-height: 1.15;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--id-color-dark, #8b93bb);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ---- Tallas -------------------------------------------------------------------------
   `--id-avatar` no se elige a ojo: es el alto de las dos líneas, o sea
   nombre·1.15 + título·1.15 + 1px de separación, redondeado hacia arriba. Cambiar un
   tamaño de letra sin recalcularlo deja al avatar cubriendo una línea y media. */

/* Barra del menú: 1rem·1.15 + 0.68rem·1.15 + 1 = 32. */
.bba-id-menu { --id-avatar: 34px; --id-name: 1rem; --id-title: 0.68rem; --id-badge: 0.95rem; }

/* Tarjeta del perfil: es la vitrina —se ve el avatar propio en grande— así que va por
   encima del mínimo de las dos líneas (33.5). */
.bba-id-card { --id-avatar: 42px; --id-name: 1.05rem; --id-title: 0.72rem; --id-badge: 1.1rem; --id-gap: 11px; }

/* Filas del panel social: 0.95rem·1.15 + 0.6rem·1.15 + 1 = 30. El techo real de la fila lo
   fijan los botones de acción (34px), así que a esta talla la fila NO crece. */
.bba-id-row { --id-avatar: 32px; --id-name: 0.95rem; --id-title: 0.6rem; --id-badge: 0.9rem; --id-gap: 8px; }

/* Marcador de partida: 1.05rem·1.15 + 0.62rem·1.15 + 1 = 32. */
.bba-id-hud { --id-avatar: 34px; --id-name: 1.05rem; --id-title: 0.62rem; --id-badge: 0.95rem; --id-gap: 8px; }

/* Presentación VS: en `em`, no en píxeles. El nombre es un `clamp()` que va de 1.5rem a
   3.2rem según la pantalla, así que el bloque entero tiene que escalar con él en vez de
   quedarse con un tamaño fijo que se vería enano en el cartel. */
.bba-id-vs {
  --id-avatar: 1.5em;
  --id-name: 1em;
  --id-title: 0.3em;
  --id-badge: 0.6em;
  --id-gap: 0.34em;
}

/* Cada nombre va en el color con el que ese jugador se va a ver en la partida: la
   presentación es lo que enseña "este de acá sos vos". Las variables las escribe
   Skins al fijar la apariencia de la partida, justo antes de mostrar esto; los
   valores de reserva son los colores por defecto de siempre. */

.vs-p1 {
  color: var(--p1-color, #4fd1ff);
  text-shadow: 0 0 18px var(--p1-glow, rgba(79, 209, 255, 0.75)), 3px 3px 0 var(--p1-shade, rgba(10, 58, 85, 0.95));
  animation: vsInLeft 0.55s cubic-bezier(0.2, 1.4, 0.4, 1) both;
}

.vs-p2 {
  color: var(--p2-color, #ff6bd6);
  text-shadow: 0 0 18px var(--p2-glow, rgba(255, 107, 214, 0.75)), 3px 3px 0 var(--p2-shade, rgba(96, 22, 73, 0.95));
  animation: vsInRight 0.55s cubic-bezier(0.2, 1.4, 0.4, 1) 0.15s both;
}

.vs-tag {
  font-family: 'BBA Titles', 'Courier New', monospace;
  font-weight: 900;
  font-size: 2rem;   /* era clamp(1.1rem, 3.6vw, 2rem), mismo caso */
  letter-spacing: 0.35em;
  color: var(--vs-accent, #ffe066);
  text-shadow: 0 0 14px var(--vs-accent-glow, rgba(255, 224, 102, 0.8)), 2px 2px 0 var(--vs-accent-shade, rgba(90, 70, 0, 0.95));
  animation: vsPop 0.3s 0.5s cubic-bezier(0.2, 1.6, 0.4, 1) both, vsFlicker 1.3s 1s steps(2) infinite;
}

@keyframes vsInLeft {
  from { opacity: 0; transform: translateX(-700px) skewX(-7deg); }
  to { opacity: 1; transform: translateX(-14px) skewX(-7deg); }
}
@keyframes vsInRight {
  from { opacity: 0; transform: translateX(700px) skewX(-7deg); }
  to { opacity: 1; transform: translateX(14px) skewX(-7deg); }
}
@keyframes vsPop {
  from { opacity: 0; transform: scale(3.2); }
  to { opacity: 1; transform: scale(1); }
}
@keyframes vsFlicker { 50% { opacity: 0.72; } }

.vs-overlay.closing { animation: vsFadeOut 0.3s forwards; }
@keyframes vsFadeOut { to { opacity: 0; } }

/* El lienzo escala CON el escenario en vez de tener sus propios topes contra el viewport:
   así la partida siempre llena el mismo marco, en cualquier pantalla. La proporción la
   fija aspect-ratio, no los atributos width/height del elemento (que siguen definiendo el
   espacio de coordenadas 800x450 en el que dibuja el juego). */
/* El lienzo toma TODO el alto que sobra tras el HUD y la línea de controles, y deriva su
   ancho de la proporción — nunca al revés. Así la partida llena el escenario sin que
   nada desborde, en cualquier escala. Los atributos width/height del elemento siguen
   definiendo el espacio de coordenadas 800x450 en el que dibuja el juego: esto es solo
   el tamaño en pantalla. */
#game-canvas {
  background: #0b1020;
  border: 2px solid var(--stage-border);
  border-radius: 10px;
  flex: 1 1 0;
  min-height: 0;
  width: auto;
  max-width: 100%;
  aspect-ratio: 804 / 454;
  align-self: center;
  /* SIN `image-rendering: pixelated`. Lo tuvo, y era lo que rompía la imagen: el búfer medía
     800x450 fijos y se agrandaba entre 1.53x y 2.30x con vecino más cercano, o sea en
     bloques. Ahora el búfer sigue al tamaño real en pantalla (client/src/canvasScale.js), así
     que no hay agrandado que suavizar u obstinar — y si alguna vez lo hay (por el tope de
     resolución), el suavizado del navegador es lo correcto para un dibujo vectorial. */
}

.controls-hint {
  color: #6a72a0;
  font-size: 0.85rem;
  margin-top: 10px;
}

#hud-overlay {
  position: relative;
  width: 100%;
  max-width: 800px;
  display: flex;
  flex-direction: column;
  align-items: center;
  pointer-events: none;
}

.hud-top {
  display: flex;
  align-items: center;
  gap: 14px;
  font-size: 1.3rem;
  font-weight: 700;
  margin-bottom: 6px;
}

.hud-score { color: #ffe066; }

/* Panel dual de jugadores: azul a la izquierda, rojo a la derecha, con el cronómetro y
   el marcador de rondas del campeonato al centro. La jerarquía tipográfica es a
   propósito: los números grandes (cronómetro, puntaje) transmiten presión; el detalle
   (estadísticas propias del juego) queda chico y secundario. */
.hud-panels {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: stretch;
  gap: 10px;
  width: 100%;
  margin-bottom: 8px;
}

.hud-panel {
  border-radius: 10px;
  padding: 8px 12px;
  border: 1px solid;
  background: rgba(8, 10, 24, 0.72);
  min-width: 0;
}

/* El acento de cada lado del marcador sigue el color que ese jugador eligió: si en el
   campo se ve amarillo, su panel también. Las variables las fija el marcador al armarse
   (ver hud.js); los valores de reserva son los colores de siempre. */
.hud-panel-blue { border-color: var(--p1-color, #2b6fe0); box-shadow: inset 0 0 0 1px rgba(79, 140, 255, 0.15); }
.hud-panel-red { border-color: var(--p2-color, #e0392b); box-shadow: inset 0 0 0 1px rgba(255, 90, 79, 0.15); text-align: right; }

/* La identidad en el marcador es el bloque de perfil compartido (talla `hud`); los colores
   del nombre y del título los escribe hud.js con el color RESUELTO del asiento.

   El panel derecho está alineado a la derecha, y el bloque se va con él COMO UN TODO: lo
   que se mueve al borde es el bloque entero, no sus piezas. Antes acá se invertía el orden
   interno (`row-reverse`) para dejar el avatar contra el canto, y eso hacía que los dos
   jugadores se leyeran distinto — el avatar a un lado en uno y al otro en el otro. Ahora
   el avatar está siempre a la izquierda de su nombre, en las cinco vistas del juego. */
.hud-panel > .bba-id {
  margin-bottom: 2px;
  /* Ajustado al contenido y no al panel: es lo que permite empujarlo al canto derecho con
     `margin-left: auto`. El tope del 100% conserva los puntos suspensivos del nombre. */
  width: fit-content;
  max-width: 100%;
}
.hud-panel-red > .bba-id { margin-left: auto; }

.hud-panel-scores {
  display: flex;
  gap: 12px;
  margin-top: 2px;
}
.hud-panel-red .hud-panel-scores { justify-content: flex-end; }

.hud-score-block { display: flex; flex-direction: column; line-height: 1.05; }
.hud-score-block-label {
  font-size: 0.74rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: #9aa3c7;
}
.hud-score-block-value {
  font-family: 'BBA Score', 'Consolas', monospace;
  font-size: 1.7rem;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  color: #ffe066;
}
.hud-score-block-global .hud-score-block-value { color: #7cff6b; }

/* El poder guardado vive en UN solo lugar: el badge-burbuja de la esquina del canvas
   (PowerFx.drawStoredBadge) — el panel del HUD ya no lo duplica. */

/* Stats de la disciplina: filas etiqueta/valor alineadas (fáciles de escanear). */
.hud-panel-stats {
  margin-top: 4px;
  display: flex;
  flex-direction: column;
  gap: 1px;
}
.hud-stat-row {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  min-width: 138px;
}
.hud-panel-red .hud-panel-stats { align-items: flex-end; }
.hud-stat-label {
  font-size: 0.74rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: #6a72a0;
  align-self: center;
}
.hud-stat-value {
  font-size: 0.95rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: #d9deff;
}

.hud-center {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 5px;
  min-width: 136px;
}

.hud-center-block {
  display: flex;
  flex-direction: column;
  align-items: center;
  line-height: 1.15;
}

.hud-caption {
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: #6a72a0;
  white-space: nowrap;
}

/* Marcador INTERNO del juego actual (puntos a 5, mejor de 3, vidas, tamaño...):
   cada número en el color de su jugador. */
.hud-roundscore {
  font-family: 'BBA Score', 'Consolas', monospace;
  font-size: 1.55rem;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
}
.hud-rs-p1 { color: var(--p1-color, #4fd1ff); }
.hud-rs-p2 { color: var(--p2-color, #ff6bd6); }
.hud-rs-sep { color: #6a72a0; margin: 0 6px; font-weight: 400; }

.hud-timer {
  /* Score es de ancho fijo por diseño: el cronómetro no tiembla al bajar de 1:00 a 0:59. */
  font-family: 'BBA Score', 'Consolas', 'Courier New', monospace;
  font-size: 2.1rem;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.03em;
  color: #f0f2ff;
  transition: color 0.12s ease, text-shadow 0.12s ease;
}

/* "De vez en cuando parpadea": clase agregada/quitada por JS a intervalos
   irregulares (no un ritmo constante), simulando una señal de alerta/presión. */
.hud-timer-blink {
  color: #ff4f4f;
  text-shadow: 0 0 10px rgba(255, 79, 79, 0.85);
}

.hud-rounds {
  font-size: 1.25rem;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  color: #ffe066;
}

.result-subtitle {
  color: #9aa3c7;
  font-size: 0.95rem;
  margin-top: -8px;
}
.result-subtitle strong { color: #d9deff; }

.hud-banner {
  position: absolute;
  top: 40%;
  left: 50%;
  transform: translate(-50%, -50%);
  font-size: 1.8rem;
  font-weight: 800;
  letter-spacing: 0.06em;
  background: rgba(5, 6, 12, 0.75);
  padding: 14px 28px;
  border-radius: 10px;
  border: 1px solid #2a2f4a;
  animation: bannerFade 1.8s ease forwards;
}

.hud-banner-round { color: #7cff6b; }

@keyframes bannerFade {
  0% { opacity: 0; transform: translate(-50%, -60%); }
  15% { opacity: 1; transform: translate(-50%, -50%); }
  80% { opacity: 1; }
  100% { opacity: 0; }
}

#result-screen h2 { font-size: 2rem; }

/* Victoria y derrota son video. Los flecos de cotillón llegan hasta el borde del cuadro y
   ahí aparecen y desaparecen de golpe, que es lo que se quería disimular.
   Se resuelve con una MÁSCARA de degradado en arriba y abajo, no con un marco ni con
   transparencia dentro del archivo:
   - Un marco empeora el problema en vez de esconderlo: dibuja una línea justo donde el
     cotillón se corta, o sea que señala el corte.
   - El archivo no puede llevar la transparencia: H.264 no tiene canal alfa. Habría que
     pasar a WebM/VP9, que en iOS no reproduce — inaceptable para un juego que apunta a
     las cuatro plataformas.
   La máscara no toca el video, se ajusta en una línea y funciona porque el arte está sobre
   negro y detrás hay un fondo casi negro: el fleco se disuelve en vez de cortarse.
   El tramo es corto (10%) para que no se coma la cabeza ni el cartel. */
.result-image {
  width: 360px;
  height: auto;
  margin-bottom: 8px;
  /* Los cuatro bordes: el cotillón también se corta a los lados. Son DOS degradados que
     se cruzan, y el modo de composición es lo que hace que se corten entre sí en vez de
     sumarse — sin `intersect` cada uno taparía lo que el otro desvanece y no se vería
     ningún efecto. El tramo lateral es más corto (8%) que el vertical (10%) porque el
     cuadro es más alto que ancho: en píxeles, ambos desvanecen algo parecido. */
  -webkit-mask-image:
    linear-gradient(to bottom, transparent 0%, #000 10%, #000 90%, transparent 100%),
    linear-gradient(to right, transparent 0%, #000 8%, #000 92%, transparent 100%);
  -webkit-mask-composite: source-in;
  mask-image:
    linear-gradient(to bottom, transparent 0%, #000 10%, #000 90%, transparent 100%),
    linear-gradient(to right, transparent 0%, #000 8%, #000 92%, transparent 100%);
  mask-composite: intersect;
}

/* ---- Menú mobile-first unificado (idéntico en web/PC/iOS/Android) ----------------
   Una sola columna que escala por tamaño de viewport; nunca se reordena. Botones de
   navegación grandes (≥48px de alto) aptos para touch y para mouse por igual. */

/* ---- Fondo animado del menú -----------------------------------------------------
   Tres capas apiladas, cada una con un motivo:
     0. el arte en ESTÁTICO como fondo del contenedor,
     1. el video encima,
     2. un velo que oscurece el centro,
     3. la interfaz.
   El estático no es redundante: es lo que se ve mientras el video carga, lo que queda si
   el autoplay está bloqueado (iOS en bajo consumo lo bloquea aunque esté silenciado) y lo
   único que se ve con prefers-reduced-motion. Pesa 166 KB contra los 2.4 MB del video.

   El estático y el video SIEMPRE son el mismo arte: si se cambia uno hay que cambiar el
   otro, o el menú parpadea de un fondo a otro al arrancar la reproducción. Van en pares,
   y cambiar de fondo son tres referencias: esta, el `poster` y el `<source>` de
   index.html. Montado va `bg.webp` + `menu-bg.mp4`; los otros dos candidatos (bg2, y bg3
   que genera `devtools/make-menu-bg.js`) están retirados con prefijo `RETIRAR-`, o sea
   siguen en disco pero fuera del paquete. La elección no es definitiva: volver a
   cualquiera de ellos es sacarles el prefijo y apuntar estas tres referencias. */
/* La capa vive FUERA de las pantallas y se posiciona sola, con el mismo tamaño y la misma
   escala que ellas, para quedar exactamente detrás de la que esté a la vista. Va primera
   en el documento, así que las pantallas —que son elementos posicionados— pintan encima
   sin necesidad de z-index.
   El borde va transparente y no ausente: con `box-sizing: border-box` eso deja la caja
   interior del mismo tamaño que la de una pantalla, y el fondo se recorta en el mismo
   sitio que el marco que se dibuja arriba. */
.ambiente {
  position: fixed;
  left: 50%;
  top: 50%;
  width: var(--stage-w);
  height: var(--stage-h);
  transform: translate(-50%, -50%) scale(var(--stage-scale));
  transform-origin: center center;
  border: 2px solid transparent;
  border-radius: 12px;
  overflow: hidden;
  pointer-events: none;
  background-image: url('assets/sprites/bg.webp');
  background-size: cover;
  background-position: center;
}

/* El video se recorta al MARCO, no al viewport: `absolute` dentro de la pantalla, que
   es el escenario. Es lo que hace que el fondo del menú ocupe exactamente el área de una
   partida y que al empezar a jugar nada cambie de tamaño. */
.menu-bg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover; /* recorta por el lado largo, nunca deforma */
  z-index: 0;
  pointer-events: none;
  /* Capa propia. El relevo entre los dos fondos funde la opacidad de uno de ellos, y sin
     esto esa mezcla obliga a recomponer el escenario entero —que además está escalado y
     recortado con esquinas redondeadas—: se medían 3-4 cuadros a 33ms en cada vuelta. */
  will-change: opacity;
}

/* Velo de legibilidad. El arte es MUY brillante y la interfaz va encima: sin esto los
   botones y el texto compiten con cuadritos de neón a contraste pleno. Oscurece el
   centro (donde viven el logo y la navegación) y deja respirar los bordes, que es donde
   el arte tiene su gracia. */
.ambiente::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background: radial-gradient(ellipse 72% 68% at 50% 46%,
    rgba(5, 6, 12, 0.84) 0%, rgba(5, 6, 12, 0.58) 55%, rgba(5, 6, 12, 0.2) 100%);
}

/* El de los dos que está esperando su turno. Se oculta con opacidad y no con `display`
   ni `visibility` a propósito: tiene que seguir siendo un elemento que el navegador
   decodifica, porque justamente lo que se busca es que su primer cuadro ya esté listo
   cuando le toque aparecer. Ver menuBg.js. */
.menu-bg-espera { opacity: 0; }

/* Con movimiento reducido se cae al arte estático, que ya está debajo. */
@media (prefers-reduced-motion: reduce) {
  .menu-bg { display: none; }
}

/* Disposición apaisada: el logo al costado y la navegación en UNA columna, como siempre.
   El escenario es 16:9 porque en móvil se juega en horizontal, y en un rectángulo así
   apilar logo + nueve entradas verticalmente no entra sin desplazar. Poniendo el logo al
   lado se usa el ancho que sobra, la columna conserva su orden y su tamaño táctil, y
   todo entra sin barras.
   El marco ya no es del menú sino del escenario: el menú solo se acomoda dentro. */
.menu-shell {
  width: 100%;
  height: 100%;
  display: grid;
  grid-template-columns: var(--col-izq) minmax(0, var(--col-der));
  align-items: center;
  justify-content: center;
  gap: 8px var(--col-gap);
  position: relative;
  z-index: 2;
}

/* Columna izquierda: identidad de marca, abarcando las tres filas que genera la columna
   derecha (identidad, panel visible y línea de estado). Se usa `span 3` y no `1 / -1`
   porque `-1` apunta al final de la grilla EXPLÍCITA, y acá las filas son implícitas:
   con `-1` el logo se quedaba en su propia fila y el menú volvía a apilarse. */
/* (la colocación del logo está más arriba, junto a la de la pantalla de cuentas: las dos
   comparten la misma regla para que caiga en el mismo punto exacto) */
/* La sala de espera (buscando rival / sala privada con su código) es un panel más y va en la
   misma columna que el resto. Sin nombrarla acá caía en la columna 1 por ser el siguiente
   hueco libre de la grilla — o sea encima del logo, con toda la mitad derecha vacía. */
.menu-shell .identity-bar,
.menu-shell .menu-panel,
.menu-shell .searching-panel,
.menu-shell #menu-status {
  grid-column: 2;
  width: 100%;
}

/* Paneles de lista (tienda, rankings, social, perfil): el logo se retira y el panel toma
   el escenario entero. Con 430px de columna una lista de catálogo no tiene forma de
   entrar; con los ~1000px del escenario, dispuesta en rejilla, sí. */
.menu-shell.wide {
  grid-template-columns: minmax(0, 100%);
  /* La fila del panel se ESTIRA: sin un alto definido, la rejilla de la lista resuelve a
     cero (flex: 1 sobre un contenedor de alto automático) y `overflow: hidden` la recorta
     entera — se veía el paginador y ningún elemento. */
  grid-template-rows: auto minmax(0, 1fr) auto;
  align-content: stretch;
  align-items: stretch;
}
.menu-shell.wide .menu-logo { display: none; }
.menu-shell.wide .identity-bar,
.menu-shell.wide .menu-panel,
.menu-shell.wide .searching-panel,
.menu-shell.wide #menu-status { grid-column: 1; }

/* Las listas largas se reparten en columnas en vez de apilarse. `auto-fill` decide
   cuántas caben según el ancho, así que funciona igual en un teléfono apaisado que en un
   monitor: siempre es el mismo escenario, solo cambia la escala. */
/* (`#highscore-list` salió de acá: una tabla de posiciones repartida en dos columnas se
   lee mal — el puesto 1 arriba a la izquierda y el 11 arriba a la derecha. Se pagina en
   una sola columna. `#friends-list` no existe en el documento: la lista de amigos es
   `#social-friends`.) */
#store-grid,
#profile-achievements,
#profile-achievements-secret {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(228px, 1fr));
  gap: 6px;
  align-content: start;
  flex: 1 1 0;
  min-height: 0;
  overflow: hidden;
}

/* Las listas de social piden más ancho que un ítem de catálogo: la fila lleva el nombre
   completo más seis acciones. Con 228px el nombre quedaba en dos letras y un puntito. */
#social-friends,
#social-requests,
#social-blocked {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(430px, 1fr));
  gap: 6px;
  align-content: start;
  flex: 1 1 0;
  min-height: 0;
  overflow: hidden;
}

/* El panel de lista se estira para que la rejilla tenga un alto medible contra el que
   calcular cuántos elementos entran. */
.menu-shell.wide .menu-panel {
  height: 100%;
  min-height: 0;
}

/* El título, la línea de estado y el botón de volver NO se encogen nunca. Sin esto, un
   panel con más contenido del que entra los aplasta primero y los empuja fuera del marco
   después: el jugador se queda sin forma visible de salir del panel. */
.menu-panel > .panel-title,
.menu-panel > .panel-status,
.menu-panel > .panel-note,
.menu-panel > .menu-back,
.menu-panel > .secondary-button {
  flex: none;
}

/* ---- Pestañas internas de un panel ----
   Cuando un panel tiene más contenido del que entra en el escenario, se reparte en
   pestañas en vez de achicar todo o dejar que se corte. La regla del proyecto es que
   nada se desplaza nunca; esto y la paginación son las dos formas admitidas de decir
   "hay más". */
.panel-tabs {
  display: flex;
  gap: 6px;
  flex: none;
}

.panel-tab {
  flex: 1;
  min-height: 34px;
  padding: 6px 10px;
  border: 1px solid #2a2f4a;
  border-radius: 8px;
  background: rgba(8, 10, 24, 0.72);
  color: #8b93bb;
  font: inherit;
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  cursor: pointer;
}
.panel-tab:hover { color: #d9deff; border-color: #4fd1ff; }
.panel-tab.active {
  color: #05060c;
  background: linear-gradient(90deg, #4fd1ff, #7cff6b);
  border-color: transparent;
  font-weight: 700;
}
.panel-tab .tab-count {
  color: inherit;
  opacity: 0.75;
  font-variant-numeric: tabular-nums;
}

/* El contenedor de paneles se lleva TODO el alto sobrante, y cada panel vive dentro de
   ese alto: es lo que convierte "se sale por abajo" en "se pagina". */
.tab-panes {
  flex: 1 1 0;
  min-height: 0;
  display: flex;
}
.tab-pane {
  flex: 1 1 auto;
  min-width: 0;
  min-height: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

/* Perfil y social: el bloque de contenido se estira, y dentro suyo la pestaña activa.
   La cadena de `min-height: 0` es obligatoria en toda la línea — basta que falte en un
   eslabón para que el hijo flexible resuelva a su alto natural y vuelva a desbordar. */
#profile-content,
#social-content {
  flex: 1 1 0;
  min-height: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

/* Las filas de personalización se paginan: el alto de una fila no es fijo (crece cuando
   hay más opciones de las que entran en una línea), así que cuántas entran se mide, no
   se calcula. */
#profile-customise,
#profile-ingame {
  flex: 1 1 0;
  min-height: 0;
  overflow: hidden;
}

#profile-identity { flex: none; }
.identity-card { margin-bottom: 0; }

/* Rankings: las dos tablas se reparten el alto sobrante en lugar de quedar fijas en
   220px con barra de desplazamiento. */
.menu-shell.wide .menu-tables {
  margin-top: 0;
  flex: 1 1 0;
  min-height: 0;
  align-items: stretch;
  flex-wrap: nowrap;
}
.menu-shell.wide .highscore-panel {
  display: flex;
  flex-direction: column;
  min-height: 0;
  flex: 1 1 0;
  width: auto;
  max-width: 460px;
}
.menu-shell.wide .highscore-panel h3 { flex: none; }

/* Práctica: las siete disciplinas en dos columnas. En una sola columna la última fila y
   el botón de volver caían fuera del escenario, y achicar los botones no es opción: el
   mínimo táctil de 44px es una regla del proyecto. */
#practice-list {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 6px;
  align-content: start;
}

/* Controles de página. Van fuera de la rejilla para no ocupar una celda. */
.pager {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 12px;
  padding-top: 4px;
  flex: none;
}
.pager-btn {
  min-width: 40px;
  min-height: 32px;
  border-radius: 8px;
  border: 1px solid #2a2f4a;
  background: rgba(16, 19, 42, 0.85);
  color: #d9deff;
  font-size: 1.1rem;
  cursor: pointer;
}
.pager-btn:hover { border-color: #4fd1ff; }
.pager-label { color: #9aa3c7; font-size: 0.85rem; font-variant-numeric: tabular-nums; }

/* (Acá había el tercer `@media (max-aspect-ratio: 1 / 1)`, y era el que se veía peor: al
   girar el teléfono a vertical el menú pasaba a una sola columna de 430 px y **el logo
   quedaba dibujado encima de los botones**. El motivo es instructivo: el logo NO es una celda
   de la rejilla, va en `position: absolute` corrido media columna derecha hacia la izquierda
   (ver `.menu-shell .menu-logo`), así que el `grid-column: 1` que la regla le ponía no lo
   movía ni un píxel — se quedaba en el centro mientras el resto se mudaba debajo de él.
   Una regla de rejilla no puede acomodar algo que está fuera de la rejilla. Ver `.screen`. */

/* El logo se acota por ALTO, no por ancho. El archivo con transparencia está recortado
   al arte (721x793, casi cuadrado), mientras que el original incluía un margen negro
   enorme (1536x1024): darle el mismo ancho de antes lo agrandaba un 110% y era la causa
   real de que el menú se sintiera gigante. A 190px de alto el logo ocupa en pantalla lo
   mismo que ocupaba el arte dentro del PNG original a 420px de ancho. */
/* 44px es el mínimo táctil que recomiendan iOS y Android, y con el logo al costado las
   nueve entradas entran justo. Como el escenario entero se escala, en una pantalla grande
   estos 44px se ven más grandes: el tamaño relativo al marco es el que importa. */
.menu-shell .nav-button,
.menu-shell .secondary-button {
  min-height: 44px;
  padding: 8px 18px;
}
.menu-shell .menu-panel { gap: 5px; }
.menu-shell .identity-bar { margin-bottom: 0; }
.menu-shell .identity-bar { align-self: center; margin-bottom: 4px; }

/* ---- La billetera: los dos saldos juntos ----
   Aparece en dos sitios —la tira del menú y la tarjeta del perfil— y es la MISMA forma en
   los dos: las cifras apiladas y alineadas al canto derecho. Una sola regla, no una por
   sitio: escritas por separado terminarían siendo dos reglas con el mismo nombre de clase
   peleándose en la cascada, que es peor que cualquiera de las dos.
   Lo que sí cambia entre sitios es el tamaño de cada cifra, y eso vive en la regla de la
   cifra. Apiladas y no en fila porque en la tira el ancho está repartido al píxel (está
   medido: sin repartirlo, el título del jugador salía cortado). */
.identity-wallet {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  line-height: 1.15;
}

.identity-coins,
.identity-gems {
  color: #ffe066;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  font-size: 0.9rem;
}
.identity-gems { color: #d9a0ff; font-size: 0.82rem; }

.menu-panel {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 10px;
}

.menu-panel-wide { width: 100%; }

.panel-title {
  font-family: 'BBA Titles', 'Segoe UI', system-ui, sans-serif;
  margin: 0 0 2px;
  font-size: 1rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: #9aa3c7;
  text-align: center;
}

.section-title {
  margin: 10px 0 4px;
  font-size: 0.85rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: #9aa3c7;
  text-align: left;
}

.nav-button {
  /* Titles, igual que en la hoja de muestra sobre la que se eligió la familia. Es de solo
     mayúsculas por diseño, así que "Play vs CPU" se dibuja "PLAY VS CPU" — el HTML no
     cambia. El símbolo de cada botón (▶, ⚡, 🏆...) no está en la cara propia y lo resuelve
     la reserva carácter por carácter: queda el icono del sistema y la palabra en la fuente
     del juego, que es justo lo que se quiere. */
  font-family: 'BBA Titles', 'Segoe UI', system-ui, sans-serif;
  min-height: 52px;
  font-size: 1.05rem;
  font-weight: 700;
  letter-spacing: 0.05em;
  padding: 12px 20px;
  border-radius: 10px;
  border: 1px solid #2a2f4a;
  background: rgba(16, 19, 42, 0.85);
  color: #d9deff;
  cursor: pointer;
  text-align: left;
}

.nav-button:hover { border-color: #4fd1ff; background: rgba(79, 209, 255, 0.08); }
.nav-button:disabled { opacity: 0.55; cursor: default; }

.nav-play { background: linear-gradient(90deg, #4fd1ff, #7cff6b); color: #05060c; border: none; }
.nav-play:hover { filter: brightness(1.1); background: linear-gradient(90deg, #4fd1ff, #7cff6b); }
.nav-mp { background: linear-gradient(90deg, #ff9b6b, #ff6bd6); color: #05060c; border: none; }
.nav-mp:hover { filter: brightness(1.1); background: linear-gradient(90deg, #ff9b6b, #ff6bd6); }

.nav-inline { min-height: 46px; flex: 0 0 auto; text-align: center; }

.nav-badge {
  display: inline-block;
  min-width: 20px;
  padding: 1px 6px;
  border-radius: 10px;
  background: #ff4f4f;
  color: #fff;
  font-size: 0.75rem;
  text-align: center;
}

.menu-back { align-self: center; }

.panel-note {
  border: 1px dashed #2a2f4a;
  border-radius: 10px;
  padding: 14px;
  color: #9aa3c7;
  font-size: 0.9rem;
}

.panel-status { min-height: 1.2em; margin: 0; font-size: 0.8rem; color: #ff9b6b; text-align: center; }

/* Por qué hay botones apagados en el menú principal. Va más chica y más apagada que una
   nota de panel normal: es una aclaración, no un aviso, y el menú ya está lleno. */
.home-locked-note {
  margin: 2px 0 0;
  padding: 8px 12px;
  font-size: 0.78rem;
  text-align: center;
  color: #7f88b0;
}

.text-input, .code-input {
  font-size: 0.95rem;
  padding: 10px 12px;
  border-radius: 8px;
  border: 1px solid #2a2f4a;
  background: #10132a;
  color: #f0f2ff;
  outline: none;
  min-width: 0;
}
.text-input:focus, .code-input:focus { border-color: #4fd1ff; }

.code-input {
  flex: 1;
  text-transform: uppercase;
  letter-spacing: 0.35em;
  /* Código de sala: lleva letras además de cifras, así que va Titles (monoespaciada) y
     no Score, que solo trae dígitos. */
  font-family: 'BBA Titles', 'Consolas', 'Courier New', monospace;
  font-weight: 700;
  text-align: center;
}

.join-row, .social-add-row, .chat-send-row { display: flex; gap: 8px; }
.social-add-row .text-input, .chat-send-row .text-input { flex: 1; }

/* Sala privada en espera: el código grande, para dictarlo o compartirlo. */
.room-code-box {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  border: 1px solid #4fd1ff;
  border-radius: 10px;
  padding: 10px 22px;
  background: rgba(79, 209, 255, 0.07);
}
.room-code-label { font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.1em; color: #9aa3c7; }
.room-code-value {
  font-family: 'BBA Titles', 'Consolas', 'Courier New', monospace;
  font-size: 2rem;
  font-weight: 800;
  letter-spacing: 0.3em;
  color: #4fd1ff;
}

/* ---- Panel social ---- */

.friend-row {
  display: flex;
  align-items: center;
  gap: 8px;
  border: 1px solid #2a2f4a;
  border-radius: 10px;
  padding: 8px 12px;
  background: rgba(8, 10, 24, 0.72);
}

/* La celda de la persona: el bloque de perfil compartido (talla `row`) más la etiqueta de
   estado de la relación. `min-width: 0` es lo que deja que el nombre se recorte con puntos
   suspensivos en vez de empujar los botones de acción fuera de la fila. */
.friend-name {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 8px;
  text-align: left;
}
.friend-name .bba-id { min-width: 0; }
/* La etiqueta describe el ESTADO de la relación ("added you", "blocked"), no a la persona:
   por eso va fuera del bloque y en el gris neutro de siempre, sin el color del jugador. */
.friend-name .muted-tag {
  flex: none;
  color: #6a72a0;
  font-weight: 400;
  font-size: 0.75rem;
}

.friend-action {
  border: 1px solid #2a2f4a;
  border-radius: 8px;
  background: transparent;
  color: #9aa3c7;
  font-size: 0.8rem;
  padding: 6px 9px;
  cursor: pointer;
  min-height: 34px;
}
.friend-action:hover { border-color: #4fd1ff; color: #d9deff; }
.friend-action.danger:hover { border-color: #ff4f4f; color: #ff9b8f; }

.friend-unread {
  min-width: 20px;
  padding: 1px 6px;
  border-radius: 10px;
  background: #ff4f4f;
  color: #fff;
  font-size: 0.75rem;
  text-align: center;
}

#social-requests .friend-row { border-color: #7cff6b; }
#social-friends, #social-requests, #social-blocked {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

/* ---- Chat ---- */

.chat-messages {
  height: 300px;   /* era min(300px, 40vh): en un teléfono apaisado quedaba en 156 */
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 6px;
  border: 1px solid #2a2f4a;
  border-radius: 10px;
  background: rgba(8, 10, 24, 0.72);
  padding: 10px;
}

.chat-bubble {
  max-width: 82%;
  padding: 7px 12px;
  border-radius: 12px;
  font-size: 0.9rem;
  text-align: left;
  overflow-wrap: anywhere;
}
.chat-bubble.me { align-self: flex-end; background: rgba(79, 209, 255, 0.16); color: #d9f3ff; }
.chat-bubble.them { align-self: flex-start; background: rgba(255, 107, 214, 0.13); color: #ffdcf3; }

.chat-invite-button {
  margin-top: 6px;
  display: block;
  border: 1px solid #7cff6b;
  border-radius: 8px;
  background: rgba(124, 255, 107, 0.1);
  color: #7cff6b;
  font-weight: 700;
  padding: 6px 12px;
  cursor: pointer;
}
.chat-invite-button:hover { filter: brightness(1.2); }

/* ---- Rankings: selector geográfico en cascada ---- */

.geo-selects { display: flex; flex-direction: column; gap: 6px; }

.geo-selects select {
  font-size: 0.9rem;
  padding: 9px 12px;
  border-radius: 8px;
  border: 1px solid #2a2f4a;
  background: #10132a;
  color: #f0f2ff;
  outline: none;
}
.geo-selects select:focus { border-color: #4fd1ff; }

.menu-panel .menu-tables { margin-top: 6px; }

/* Badge/título equipados en las tablas. */
.rank-badge { flex: 0 0 auto; }
.rank-title { color: #6a72a0; font-size: 0.72rem; font-style: italic; white-space: nowrap; }

/* ---- Tienda ---- */

/* Los dos saldos en una sola línea centrada. `gap` y no un separador dibujado: el punto
   medio que se usa en otros sitios del menú separa rótulos, y acá los dos lados son cifras
   —un signo entre ellas se leería como una operación. */
.store-wallet {
  margin: 0;
  display: flex;
  justify-content: center;
  align-items: baseline;
  gap: 18px;
  flex-wrap: wrap;
}

.store-coins,
.store-gems {
  color: #ffe066;
  font-weight: 700;
  font-size: 1.05rem;
  font-variant-numeric: tabular-nums;
}
.store-gems { color: #d9a0ff; }

/* El motivo por el que los premium están apagados. Va pegado a los saldos porque es la
   respuesta a lo que uno acaba de leer ahí ("tengo 0 gemas, ¿y entonces?").

   Mismo tratamiento que `.home-locked-note` y por el mismo motivo: es una ACLARACIÓN, no un
   aviso. Sin el marco de puntos, además, porque acá el sitio está contado — medido, la
   rejilla de la tienda tiene 181px de alto y como nota de panel normal esto se llevaba 43,
   casi una cuarta parte, para decir una sola frase. Sin marco y en cuerpo chico son 18. */
.store-gems-note {
  margin: 0;
  border: none;
  padding: 2px 0 0;
  font-size: 0.78rem;
  text-align: center;
  color: #7f88b0;
}

.store-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 8px;
}

.store-item {
  border: 1px solid #2a2f4a;
  border-radius: 10px;
  background: rgba(8, 10, 24, 0.72);
  padding: 10px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
}

.store-item-name { font-weight: 700; color: #d9deff; font-size: 0.95rem; }
.store-item-slot { font-size: 0.68rem; text-transform: uppercase; letter-spacing: 0.08em; color: #6a72a0; }
.store-item-price { color: #ffe066; font-weight: 700; font-size: 0.85rem; }

.store-buy {
  width: 100%;
  min-height: 38px;
  border-radius: 8px;
  border: none;
  background: linear-gradient(90deg, #4fd1ff, #7cff6b);
  color: #05060c;
  font-weight: 700;
  cursor: pointer;
}
.store-buy:disabled { opacity: 0.5; cursor: default; }

/* Lo premium NO se pinta acá. Su color vive en la sección de dirección de arte del final de
   esta hoja, junto a las reglas que le dan forma al botón y a la tarjeta: escrito en este
   punto quedaba tapado por ellas —`background: none` + `border-image`— y el botón de gemas
   salía verde como cualquier otro. Buscar "Premium" más abajo. */

.store-equip {
  width: 100%;
  min-height: 38px;
  border-radius: 8px;
  border: 1px solid #2a2f4a;
  background: transparent;
  color: #9aa3c7;
  font-weight: 700;
  cursor: pointer;
}
.store-equip:hover { border-color: #4fd1ff; color: #d9deff; }
.store-equip.equipped { border-color: #7cff6b; color: #7cff6b; }

/* ---- Perfil / achievements ---- */

/* ---- Perfil: tarjeta de identidad y personalización ---- */

/* Compacta a propósito. Es una tarjeta de vista previa, no el contenido del panel: cada
   píxel que se lleva se lo quita a las filas que hay que poder tocar, y con la tarjeta
   grande la pestaña "Look" pasaba a dos páginas teniendo sitio libre debajo. */
.identity-card {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 8px 14px;
  margin-bottom: 0;
  background: linear-gradient(135deg, rgba(79,209,255,0.10), rgba(255,107,214,0.08));
  border: 1px solid #2a2f4a;
  border-radius: 12px;
}
/* El contenido de la tarjeta es el bloque de perfil compartido (talla `card`). Se lleva
   el sitio que sobra para que las monedas queden pegadas al canto derecho. */
.identity-card > .bba-id { flex: 1; min-width: 0; }
/* El avatar propio es lo único de la tarjeta que se mira de cerca: se le da el borde
   marcado que tenía antes, que acá sí distingue el sujeto del fondo degradado. */
.identity-card .bba-id-avatar { border-width: 2px; background: #0b0e1a; }

/* Las cifras de la billetera de la tarjeta del perfil (la forma del bloque está arriba, en
   `.identity-wallet`). El rótulo "only you see this" cierra el bloque y vale para los dos
   saldos: por eso van en un contenedor y no en dos columnas sueltas — separadas, la frase
   habría que repetirla, y repetida deja de leerse.

   Los dos saldos en UNA línea, no apilados. El motivo está medido y explicado en
   renderIdentityCard: apilados, la billetera pasa a ser lo más alto de la tarjeta y le roba
   10px al panel. Acá sobra ancho y falta alto — exactamente al revés que en la tira del
   menú, que es por lo que la misma billetera se resuelve distinto en cada sitio. */
.identity-wallet-row { display: flex; align-items: baseline; gap: 12px; }
.identity-coins-value { font-size: 1.1rem; font-weight: 800; color: #ffe066; }
/* Las gemas, un punto más chicas que las monedas. No es jerarquía de importancia sino de
   frecuencia: el saldo que cambia todos los días es el de monedas, y es el que el ojo
   busca primero al abrir el perfil. */
.identity-gems-value { font-size: 0.98rem; font-weight: 800; color: #d9a0ff; }
.identity-wallet-label { display: block; font-size: 0.62rem; color: #6a72a0; text-transform: uppercase; letter-spacing: 0.08em; }

.cust-row {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 6px 0;
  border-bottom: 1px solid rgba(42,47,74,0.55);
}
.cust-label {
  min-width: 118px;
  color: #8b93bb;
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  padding-top: 5px;
}
.cust-options { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }

/* El plazo de mudanza. Va pegado al selector y no en la línea de estado del panel porque
   se lee ANTES de tocar nada: es una condición, no el resultado de un intento. */
.cust-note {
  flex: 1 1 100%;
  padding-left: 118px;
  color: #ffd76b;
  font-size: 0.68rem;
}

/* ---- Selector de ubicación en cascada (client/src/geoPicker.js) ----
   País (botón que abre el panel de banderas) y debajo un desplegable por nivel del árbol
   que exista: región, provincia, comuna. Se usa en el perfil y en el registro, así que el
   estilo cuelga de `.geo-picker` y no del panel que lo contiene. */
.geo-picker { display: flex; flex-direction: column; gap: 5px; width: 100%; }
.geo-row { display: flex; align-items: center; gap: 8px; }
/* El ancho fijo alinea los tres desplegables en columna. Sin él, "Country", "Region" y
   "Commune" tienen anchos distintos y cada control arranca en otro lado. */
.geo-label {
  min-width: 74px;
  flex: none;
  color: #6a72a0;
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
}
/* Dos clases y no una: `.text-input`, que estos desplegables también llevan, declara
   `font: inherit` MÁS ABAJO en la hoja. Con la misma especificidad gana el que va último,
   y el atajo `font:` reinicia la familia — así que la regla de una sola clase se aplicaba
   y quedaba pisada, que es de los fallos de CSS más difíciles de ver: el estilo está
   escrito, es correcto, y no hace nada. */
.geo-picker .geo-select {
  flex: 1 1 auto;
  min-width: 0;
  max-width: 260px;
  padding: 4px 8px;
  font-size: 0.82rem;
  /* CARA DEL SISTEMA, a propósito, y es la única excepción de este tipo en el menú.
     Los nombres de lugar son datos del mundo real y vienen con acentos: Ñuñoa, Peñalolén,
     Valparaíso, Curacaví, Estación Central — el 27% de las divisiones cargadas. Las tres
     tipografías propias cubren ASCII imprimible y nada más (ver client/src/fonts.js), así
     que heredarlas acá dibujaba "Nuñoa": el nombre de una comuna de 200.000 personas, mal
     escrito en su propio selector. Es texto que tiene que decir lo que dice, no una pieza
     de la interfaz — el mismo criterio por el que existe `Fonts.simbolo()`. */
  font-family: 'Segoe UI', system-ui, sans-serif;
}
.geo-country { flex: 1 1 auto; min-width: 0; max-width: 260px; justify-content: flex-start; }
.geo-loading { color: #6a72a0; font-size: 0.74rem; padding-left: 82px; }

/* En el registro las etiquetas van EN LÍNEA con su control, no encima.
   Se probaron apiladas primero y el formulario dejó de entrar: cuatro filas a doble alto
   empujaron el botón "Create Account" fuera de la pantalla, y en esta pantalla no hay
   paginador ni desplazamiento que lo rescate (regla del escenario, GDD §4.7). En línea
   cada fila mide la mitad y el formulario vuelve a caber entero. */
.auth-geo { gap: 3px; margin-bottom: 2px; }
.auth-geo .geo-label { min-width: 66px; text-align: right; }
.auth-geo .geo-select,
.auth-geo .geo-country { max-width: none; }
.auth-geo .geo-loading { padding-left: 74px; }

.cust-opt {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: #0b0e1a;
  border: 1px solid #2a2f4a;
  color: #9aa3c7;
  border-radius: 7px;
  padding: 5px 11px;
  font: inherit;
  font-size: 0.82rem;
  cursor: pointer;
}
.cust-opt:hover { border-color: #4fd1ff; color: #d9deff; }
.cust-opt.on { border-color: #4fd1ff; background: rgba(79,209,255,0.14); color: #d9deff; font-weight: 600; }

.opt-avatar { width: 20px; height: 20px; border-radius: 50%; }
.opt-swatch {
  width: 14px; height: 14px; border-radius: 4px;
  border: 1px solid rgba(255,255,255,0.25);
  display: inline-block;
}

.section-title {
  margin: 18px 0 6px;
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: #6a72a0;
  font-weight: 700;
}

.text-input {
  background: #0b0e1a;
  border: 1px solid #2a2f4a;
  color: #d9deff;
  border-radius: 6px;
  padding: 5px 9px;
  font: inherit;
}

.profile-summary {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(110px, 1fr));
  gap: 8px;
}

.profile-stat {
  border: 1px solid #2a2f4a;
  border-radius: 10px;
  background: rgba(8, 10, 24, 0.72);
  padding: 10px 6px;
  display: flex;
  flex-direction: column;
  align-items: center;
}
.profile-stat-value { font-size: 1.3rem; font-weight: 800; color: #ffe066; font-variant-numeric: tabular-nums; }
.profile-stat-label { font-size: 0.68rem; text-transform: uppercase; letter-spacing: 0.08em; color: #6a72a0; }

.achievements-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 8px;
}

/* Los rellenos están ajustados para que entren DOS filas de logros por página en vez de
   una. Con 8px más de alto por tarjeta la segunda fila no entraba y los quince logros
   pasaban de dos páginas a cuatro, con un tercio del panel vacío debajo. */
.achievement {
  border: 1px solid #2a2f4a;
  border-radius: 10px;
  background: rgba(8, 10, 24, 0.72);
  padding: 7px 9px;
  text-align: left;
}
.achievement.locked { opacity: 0.45; }
.achievement.unlocked { border-color: #ffe066; }
/* Un secreto ganado se distingue por su marca (🔮 en vez de 🏅) y por vivir en su propia
   pestaña. NO por el borde: con el arte del arcade puesto, `.achievement.unlocked` pinta
   el marco con `border-image`, y ahí un `border-color` no se ve — se probó y quedaba una
   regla que prometía un color que nunca aparecía. */
.achievement-name { font-weight: 700; color: #d9deff; font-size: 0.88rem; line-height: 1.25; }
/* Interlineado apretado en la descripción: es la que se va a dos renglones cuando el
   texto es largo, y esa diferencia entre una tarjeta y otra decidía si entraba una fila
   de logros o dos. */
.achievement-desc { color: #9aa3c7; font-size: 0.74rem; margin-top: 1px; line-height: 1.2; }
/* Pie de la tarjeta: recompensa y progreso COMPARTEN renglón. Ver el comentario de
   `tarjetaLogro` — darle línea propia a la barra costaba una fila entera de logros por
   página, porque el grid iguala el alto de todas las tarjetas de una misma fila. */
.achievement-foot {
  display: flex;
  align-items: center;
  gap: 7px;
  margin-top: 2px;
}
.achievement-coins { flex: none; color: #ffe066; font-size: 0.74rem; font-weight: 700; }
.achievement-bar {
  flex: 1 1 auto;
  height: 4px;
  border-radius: 3px;
  background: rgba(255, 255, 255, 0.1);
  overflow: hidden;
}
.achievement-bar i {
  display: block;
  height: 100%;
  background: #4fd1ff;
}
.achievement-count {
  flex: none;
  font-size: 0.66rem;
  color: #8b93bb;
  font-variant-numeric: tabular-nums;
}

/* Nota al pie de la pestaña de secretos: es un pie de página, no un aviso — no puede
   comerse el alto que necesita la rejilla de arriba. */
.panel-note-slim {
  flex: none;
  margin: 0;
  padding: 7px 12px;
  font-size: 0.76rem;
  text-align: center;
}

/* ---- Iconos de las dos monedas ----
   Sprite procedural en CSS (círculo dorado con brillo); si existe
   assets/sprites/coin.png lo reemplaza automáticamente (capa superior del
   background — misma filosofía de reemplazo sin fricción que el resto de assets). */
.coin-icon {
  display: inline-block;
  width: 1em;
  height: 1em;
  vertical-align: -0.12em;
  border-radius: 50%;
  background-image: url('assets/sprites/coin.png'),
    radial-gradient(circle at 35% 30%, #fff3b0 0%, #ffd93b 45%, #d9930d 100%);
  background-size: contain;
  background-repeat: no-repeat;
  background-position: center;
  box-shadow: inset 0 0 0 1px rgba(140, 90, 0, 0.85), 0 0 4px rgba(255, 217, 59, 0.35);
}

/* La gema sale de la lámina 08.5, de la casilla vecina a la moneda: por eso las dos se
   leen como monedas de la misma familia sin que nadie lo explique.
   Sin `border-radius` ni `box-shadow` como los de la moneda —que ahí simulan un disco
   acuñado y son parte del dibujo procedural de reserva— porque la gema NO es redonda:
   redondearla le cortaría las tres puntas, que son justamente su silueta. */
.gem-icon {
  display: inline-block;
  width: 1em;
  height: 1em;
  vertical-align: -0.12em;
  background-image: url('assets/sprites/ui/icon-gem.png');
  background-size: contain;
  background-repeat: no-repeat;
  background-position: center;
  filter: drop-shadow(0 0 3px rgba(176, 64, 255, 0.45));
}

/* ---- How to Play (video pendiente de subir; fallback a la lámina de controles) ---- */
.howtoplay-video {
  width: 100%;
  max-height: 265px;   /* 46% del alto del escenario (576), que es lo que decía 46vh */
  border: 1px solid #2a2f4a;
  border-radius: 10px;
  background: #05060c;
}

/* Los dos accesos a la hoja de controles, en una fila. Comparten el ancho en vez de apilarse
   porque el panel ya lleva el video y aquí la altura es el recurso escaso: dos botones
   apilados cuestan una fila entera de más y no dicen nada que no diga uno al lado del otro. */
.howto-controls {
  flex: none;
  display: flex;
  gap: 6px;
}
.howto-controls .nav-button { flex: 1 1 0; min-width: 0; }
/* Con entrada táctil el botón de táctil va primero: el modo activo se lee antes. Es solo el
   orden visual — el HTML no cambia, así que el recorrido con teclado sigue siendo el mismo. */
body.bba-tactil .howto-controls .howto-touch { order: -1; }

/* ---- Resumen de fin de partida ---- */
.result-coins {
  margin: 2px 0 0;
  font-size: 1.15rem;
  font-weight: 800;
  color: #ffe066;
  font-variant-numeric: tabular-nums;
}

.result-summary {
  width: 560px;
  border: 1px solid #2a2f4a;
  border-radius: 10px;
  background: rgba(8, 10, 24, 0.72);
  padding: 10px 14px;
  margin-top: 8px;
}

.result-summary table { width: 100%; border-collapse: collapse; }
.result-summary th {
  font-size: 0.68rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: #6a72a0;
  text-align: left;
  padding: 2px 6px;
}
.result-summary td {
  font-size: 0.85rem;
  color: #d9deff;
  padding: 3px 6px;
  border-top: 1px solid rgba(42, 47, 74, 0.6);
  text-align: left;
  font-variant-numeric: tabular-nums;
}
.result-summary td.rs-win { color: #7cff6b; font-weight: 700; }
.result-summary td.rs-num { text-align: right; }

/* ---- Celebración de achievements: toast + lluvia de píxeles ---- */
.result-toasts {
  position: fixed;
  top: 14vh;
  left: 50%;
  transform: translateX(-50%);
  z-index: 70;
  display: flex;
  flex-direction: column;
  gap: 8px;
  pointer-events: none;
}

.result-toast {
  border: 2px solid #ffe066;
  border-radius: 12px;
  background: rgba(8, 10, 24, 0.94);
  box-shadow: 0 0 24px rgba(255, 224, 102, 0.45);
  padding: 12px 22px;
  text-align: center;
  animation: toastIn 0.35s cubic-bezier(0.2, 1.5, 0.4, 1) both, toastOut 0.4s ease 3.4s forwards;
}
.result-toast-tag {
  display: block;
  font-size: 0.66rem;
  text-transform: uppercase;
  letter-spacing: 0.22em;
  color: #ffe066;
}
.result-toast-name { display: block; font-size: 1.15rem; font-weight: 800; color: #f0f2ff; }
.result-toast-desc { display: block; font-size: 0.78rem; color: #9aa3c7; }
.result-toast-coins { display: block; font-size: 0.85rem; font-weight: 700; color: #ffe066; margin-top: 2px; }

@keyframes toastIn { from { opacity: 0; transform: scale(2.2); } to { opacity: 1; transform: scale(1); } }
@keyframes toastOut { to { opacity: 0; transform: translateY(-16px); } }

/* Registro fijo del logro en la pantalla de resultado: lo que queda cuando el toast ya
   se fue. Es la respuesta a "gané algo pero no sé qué". */
.result-achievements {
  display: flex;
  flex-direction: column;
  gap: 6px;
  width: 420px;
  margin: 4px 0 2px;
}
.result-achievement-row {
  border: 1px solid #ffe066;
  border-radius: 10px;
  background: rgba(255, 224, 102, 0.07);
  padding: 8px 14px;
  text-align: center;
}
.result-achievement-name { display: block; font-weight: 800; color: #ffe066; font-size: 0.98rem; }
.result-achievement-desc { display: block; font-size: 0.76rem; color: #9aa3c7; }
.result-achievement-coins { display: block; font-size: 0.8rem; font-weight: 700; color: #ffe066; margin-top: 2px; }

.confetti-layer {
  position: fixed;
  inset: 0;
  z-index: 65;
  pointer-events: none;
  overflow: hidden;
}

/* Píxeles cayendo: cuadraditos con deriva horizontal (--px-drift) y rotación. */
.confetti-px {
  position: absolute;
  top: -12px;
  image-rendering: pixelated;
  animation: pxFall var(--px-dur, 2.6s) linear var(--px-delay, 0s) forwards;
  opacity: 0;
}
@keyframes pxFall {
  0% { opacity: 1; transform: translate(0, 0) rotate(0deg); }
  100% { opacity: 0.85; transform: translate(var(--px-drift, 0px), 108vh) rotate(660deg); }
}

/* ==========================================================================================
   PIEL DEL MENÚ — arte extraído de las láminas de dirección de arte
   ==========================================================================================

   Las piezas salen de assets/sprites/ui/, que genera `node devtools/art/extract.js`
   recortándolas de las láminas de assets/sprites/MATERIAPRIMA-art-direction-and-samples/. Cada
   una viste una parte del menú; no hay ninguna que no se use acá.

   Va todo junto y al final a propósito. Son reglas de una sola clase, igual que las que
   pisan, así que ganan por orden — y así se lee de un vistazo QUÉ PIEZA VISTE A QUÉ, que es
   la pregunta que uno se hace al volver. Repartidas por la hoja habría que reconstruirlo
   cada vez.

   Se usa `border-image` y no `background-image` porque las piezas son 9-slice: las esquinas
   cortadas y el glow tienen que conservar su tamaño mientras el centro se estira. Con
   `background-size` las esquinas se deformarían con cada botón de ancho distinto.

   `border-image-outset` deja que el glow se pinte FUERA de la caja sin ocupar espacio de
   maquetación. Importa acá más que en otros proyectos: el escenario recorta, y cualquier
   cosa que sume alto real puede volver a dejar contenido inalcanzable. */

/* EL MARCO OCUPA ALTO REAL. Un borde se suma al relleno que cada regla ya tenía, y con ocho
   botones en el menú principal eso fueron 107 px de más: contenido fuera del escenario, o
   sea inalcanzable. El relleno se descuenta acá, pegado a la causa, y no en la regla
   original — que sin el marco sigue estando bien.

   EL INTERIOR SALE DEL ARTE (`fill`), NO DE UN DEGRADADO CSS.
   El primer intento pintaba el relleno con un `linear-gradient` y dejaba el marco al
   `border-image`. Eran dos dibujos distintos encontrándose: donde terminaba el degradado y
   empezaba el borde había un escalón de color, y encima hacía falta un `clip-path` para que
   las esquinas cuadradas del fondo no asomaran por fuera del corte — dos diagonales
   antialiaseadas superpuestas, que es de donde venía el aspecto tosco.

   Ahora el relleno ES la pieza. No hay escalón porque no hay dos imágenes, no hace falta
   recortar porque el arte ya trae sus esquinas transparentes, y el glow exterior vuelve
   (`border-image-outset`) porque ya no lo corta nada. Lo que permitió volver a `fill` es que
   las zonas estirables de cada pieza se sanean al extraerlas — ver devtools/art/extract.js;
   sin eso, los píxeles decorativos del centro se estiraban como vetas. */
/* `.primary-button` va en esta lista y no solo en la de más abajo. Estaba fuera, así que
   recibía `background: none` pero NUNCA el `border-width` ni el corte que hacen visible al
   `border-image`: quedaba transparente con su texto oscuro (#04182a) sobre fondo oscuro.
   Se notaba como un "BACK TO MENU" casi ilegible en la pantalla final, y también afectaba a
   Log In, Create Account y Play as Guest — o sea a los tres botones de la primera pantalla
   que ve un jugador nuevo. */
.nav-button,
.secondary-button,
.pager-btn,
.panel-tab,
.store-buy,
.cust-opt,
.primary-button {
  background: none;
  border: 12px solid transparent;
  border-image: url('/assets/sprites/ui/button-secondary.png') 16 fill / 12px / 3px stretch;
  border-radius: 0;
  box-shadow: none;
  padding-top: 0;
  padding-bottom: 0;
}

/* Las fichas del personalizador (none, Rookie, Paddle…) son botones como cualquier otro y
   se habían quedado con el estilo anterior: mismo panel, dos lenguajes gráficos distintos.
   Son chicas, así que el marco va proporcionalmente más fino. */
.cust-opt {
  border-width: 7px;
  border-image-width: 7px;
  padding-left: 7px;
  padding-right: 7px;
  border-radius: 0;
}
.cust-opt.on {
  border-image-source: url('/assets/sprites/ui/row-selected.png');
  border-image-slice: 15 fill;
  background: none;
  color: #f2f3ff;
}
.nav-button { padding-left: 12px; padding-right: 12px; }
.secondary-button { padding-left: 12px; padding-right: 12px; margin-top: 2px; }
.primary-button { padding-left: 20px; padding-right: 20px; margin-top: 10px; }
.store-buy { border-width: 9px; border-image-width: 9px; }

/* Dentro del menú manda una regla de DOS clases (`.menu-shell .nav-button`) que fija 44 px
   de alto y 8 px de relleno, calibrados para que las nueve entradas entren justo. Una regla
   de una sola clase no le gana por más abajo que esté, así que la compensación tiene que
   repetir esa especificidad — si no, el marco se suma al relleno y son 101 px fuera del
   escenario. El borde baja a 9 px acá: con 44 px de alto, 12 px arriba y abajo dejaban al
   texto sin sitio y el botón crecía igual. */
.menu-shell .nav-button,
.menu-shell .secondary-button {
  padding: 0 14px;
  border-width: 9px;
  border-image-width: 9px;
}
/* ---- ESTADOS: LUZ, NO OTRA PIEZA -------------------------------------------------------

   Ningún estado cambia de imagen. Los cuatro comparten el mismo `border-image`, el mismo
   `border-width` y el mismo corte, así que la silueta es matemáticamente idéntica: no puede
   encogerse, ni adelgazar, ni perder borde, porque es el mismo dibujo.

   Antes cada estado era su propio recorte de la lámina, y medido no coincidían — el borde
   sólido del primario normal mide 328x127 con 19 px de glow a la izquierda, y el del hover
   343x190 con 7. Metidos en el mismo borde, el marco se dibujaba corrido en cada estado.

   Esto no es una simplificación: es lo que la lámina 02 pide literalmente. Del hover dice
   "brighter glow"; del pressed "inner darkening · glow reduced · 1-2px visual push down";
   del disabled "desaturated · low contrast · no glow". Todo iluminación y profundidad.

   El resplandor del hover sale de `drop-shadow`, que sigue el ALFA de la pieza: se enciende
   alrededor de la silueta real, no de un rectángulo, y se pinta fuera sin ocupar
   maquetación — así que tampoco puede mover nada. */
/* EL HOVER ES LUZ QUE SALE, NO PINTURA QUE SE APLICA.
   `drop-shadow` se calcula sobre el ALFA de la pieza, así que el resplandor nace del
   contorno real —esquinas cortadas incluidas— y se pinta HACIA AFUERA. El dibujo sólido no
   se toca: mismo borde, mismo color, mismo grosor.

   Antes esta regla llevaba además `brightness` y `saturate`, y eso sí entra en el dibujo:
   aclaraba el borde y lo desteñía. Se fueron. El estado se comunica entero con el halo.

   Tres sombras apiladas en vez de una: un núcleo ceñido al borde y dos coronas cada vez más
   abiertas, la última en otro tono. Eso es lo que da la sensación de degradado en la luz —
   una sola sombra da un halo plano. Cada familia solo declara sus dos colores. */
.nav-button,
.secondary-button,
.pager-btn,
.panel-tab,
.store-buy,
.primary-button,
.cust-opt {
  --glow-cerca: rgba(124, 56, 216, 0.8);
  --glow-lejos: rgba(9, 189, 219, 0.42);
  transition: filter 0.14s ease-out, transform 0.07s ease-out;
}
/* NINGÚN ESTADO PINTA FONDO. Las reglas de hover anteriores al arte sí lo hacían:
   `.nav-button:hover` un celeste translúcido y `.nav-play/.nav-mp:hover` su degradado. Un
   `background` cubre el RECTÁNGULO entero del elemento, así que asomaba por fuera de las
   esquinas cortadas y por detrás del halo — se veía como una caja brillante detrás del
   botón. Llevan `:hover`, o sea que le ganan por especificidad a la regla base del arte y
   hay que apagarlas acá con la misma.

   El botón Controls nunca lo mostró porque su regla de hover no trae fondo: por eso el
   defecto salía en los ocho primeros del menú y no en el noveno. */
.nav-button:hover,
.nav-play:hover,
.nav-mp:hover,
.secondary-button:hover,
.pager-btn:hover,
.panel-tab:hover,
.store-buy:hover,
.primary-button:hover,
.cust-opt:hover {
  background: none;
  border-color: transparent;
  filter:
    drop-shadow(0 0 2px var(--glow-cerca))
    drop-shadow(0 0 7px var(--glow-cerca))
    drop-shadow(0 0 17px var(--glow-lejos));
}
/* El hundido es de 1 px, que es lo que dice la lámina. `transform` no participa de la
   maquetación, así que el botón se hunde sin empujar a sus vecinos ni cambiar de tamaño. */
.nav-button:active,
.secondary-button:active,
.pager-btn:active,
.panel-tab:active,
.store-buy:active,
.primary-button:active,
.cust-opt:active {
  filter: brightness(0.8) saturate(0.94) contrast(1.06);
  transform: translateY(1px);
}
/* El desactivado conserva la forma entera: era un `opacity` que además volvía translúcido
   el botón y dejaba ver el fondo animado a través suyo.

   LOS TRES NÚMEROS SALEN DE LA LÁMINA, MEDIDOS. Antes eran a ojo, y estaban lejos: el
   desactivado de la lámina no es un gris apagado, es un botón desteñido y aplanado que
   conserva algo de su color. Comparando en las láminas 02 y 03 la región del botón
   desactivado contra la del normal (píxeles opacos, promedio de saturación y luminosidad,
   y contraste como distancia entre el percentil 5 y el 95):

     saturación   27% y 35% de la del normal  ->  grayscale(0.68) deja 32%
     luminosidad  75% y 70%                   ->  brightness(0.72)
     contraste    65% y 67%                   ->  contrast(0.66)

   Las dos láminas coinciden, así que el tratamiento es uno solo para las dos familias. Lo
   que había (grayscale 0.92, contrast 0.92) desteñía de más y aplanaba de menos: un gris
   plano en vez del "desaturated · low contrast · no glow" que la lámina pide. */
.nav-button:disabled,
.secondary-button:disabled,
.pager-btn:disabled,
.primary-button:disabled,
.store-buy:disabled,
.nav-button:disabled:hover,
.secondary-button:disabled:hover,
.pager-btn:disabled:hover,
.primary-button:disabled:hover,
.store-buy:disabled:hover {
  filter: grayscale(0.68) brightness(0.72) contrast(0.66);
  opacity: 1;
  transform: none;
  /* "No glow": las reglas de hover de arriba llevan `:hover` y le ganarían a esta por
     especificidad, así que los dos selectores `:disabled:hover` están para apagar el halo.
     Sin ellos, un botón apagado se ilumina al pasarle el ratón por encima — que es
     exactamente lo contrario de lo que tiene que comunicar. */
  cursor: not-allowed;
}

/* Los controles chicos no aguantan 12 px de borde arriba y abajo: la pestaña mide 34 px de
   alto y el paginador 32, así que 24 se irían en marco y el texto quedaría sin sitio. */
.pager-btn,
.panel-tab {
  border-width: 8px;
  border-image-width: 8px;
  padding-left: 6px;
  padding-right: 6px;
}

/* Lo elegido usa la pieza de "seleccionado", que recorre cian→magenta→ámbar; su halo sigue
   esos mismos tonos. */
.panel-tab.active,
.cust-opt.on {
  --glow-cerca: rgba(9, 189, 219, 0.8);
  --glow-lejos: rgba(221, 33, 181, 0.45);
}
.panel-tab.active {
  border-image-source: url('/assets/sprites/ui/row-selected.png');
  border-image-slice: 15 fill;
  color: #f2f3ff;
}

/* Acción principal: el botón verde de la lámina 02. El degradado cian→verde ya viene DENTRO
   de la pieza, así que no se repite en CSS — repetirlo era el escalón de color. */
.primary-button,
.nav-play,
.store-buy {
  border-image-source: url('/assets/sprites/ui/button-primary.png');
  background: none;
  color: #04182a;
}
/* Cada familia enciende en SU color: el violeta genérico sobre un botón verde lo ensucia en
   vez de encenderlo. Los dos tonos salen de la paleta oficial (lámina 00.7) y son los mismos
   que recorren la propia pieza, así que la luz parece emitida por el botón y no puesta
   encima. Solo se declaran los colores — la forma del halo la pone la regla de arriba. */
.primary-button,
.nav-play,
.store-buy {
  --glow-cerca: rgba(101, 241, 121, 0.8);
  --glow-lejos: rgba(9, 189, 219, 0.45);
}

/* Premium (Arcade Gems): el MISMO botón girado de tono, igual que multijugador. No es otro
   dibujo — el giro está medido y anotado en devtools/art/extract.js: 122° llevan el tono
   medio del primario (154°) exactamente sobre el de la gema (276°), así que el botón y el
   icono que lleva impreso son el mismo violeta y no dos parecidos.
   Va DESPUÉS de la regla de arriba porque tiene su misma especificidad: escrita antes, el
   verde ganaba por orden. El texto pasa de azul marino a ciruela por la misma razón que el
   halo cambia de color — un tono ajeno sobre el botón lo ensucia en vez de encenderlo. El
   brillo de la pieza no cambió (el reteñido solo gira tono y saturación), así que un texto
   igual de oscuro sigue legible. */
.store-buy-gems {
  border-image-source: url('/assets/sprites/ui/button-gems.png');
  color: #20063a;
  --glow-cerca: rgba(176, 64, 255, 0.8);
  --glow-lejos: rgba(224, 160, 255, 0.45);
}

/* Multijugador: el mismo botón girado de tono, no otro dibujo. */
/* `#play-mp-button` lleva su degradado en una regla de ID (style.css:201), y un ID le gana a
   cualquier selector de clase por más abajo que esté. Es el único de todo el menú, y por eso
   era el único botón al que no le llegaba el arte. Hay que repetir la especificidad. */
.nav-mp,
#play-mp-button {
  border-image-source: url('/assets/sprites/ui/button-mp.png');
  background: none;
  color: #2a0f18;
}
.nav-mp {
  --glow-cerca: rgba(255, 130, 90, 0.8);
  --glow-lejos: rgba(221, 33, 181, 0.45);
}

/* ---- ICONOS DEL MENÚ (lámina 08) -------------------------------------------------------

   Antes eran emojis del sistema, o sea un dibujo distinto en cada plataforma y ajeno al
   juego. Ahora salen de la misma biblioteca que los botones.

   Van en `::before` y no en el HTML: el icono es decoración, no contenido. Así no lo lee un
   lector de pantalla, no entra en la selección de texto y —lo que importa acá— cambiarlo no
   toca el marcado. La geometría se declara una vez y cada botón solo nombra su archivo; el
   `::before` no se dibuja mientras nadie declare `content`, así que los botones sin icono
   (las siete disciplinas de Practice) no reservan hueco.

   La medida va en `em` para que el icono siga al tipo de letra de su botón: el del menú
   principal es mayor que el de un Back, y con píxeles habría que mantener dos tamaños. */
/* El icono se centra en la CAJA del botón, no en la línea base del texto.
   `vertical-align` alinea contra la escritura, y ahí el centro depende del tipo de letra y
   del alto de la línea: cada botón quedaba con el icono un poco corrido y ninguno en el
   medio real. Repartiendo el contenido con flex y `align-items: center` el icono cae en el
   centro vertical del botón por construcción, y sigue estándolo cuando el texto ocupa dos
   renglones — que es lo que pasa en las siete disciplinas de Practice.

   El archivo de cada icono es un cuadrado con el dibujo centrado (ver `encuadrar` en
   devtools/art/lib/imagen.js), así que la caja de 1.5em basta para anclarlos a todos: sin
   eso habría que corregir icono por icono, porque el "VS" es ancho y bajo y la llave alta y
   fina. */
.nav-button,
.secondary-button {
  display: flex;
  align-items: center;
  gap: 0.5em;
}
.nav-button { justify-content: flex-start; }
.secondary-button { justify-content: center; }

.nav-button::before,
.secondary-button::before {
  flex: none;
  width: 1.5em;
  height: 1.5em;
  background: no-repeat center / contain;
}

/* Los iconos de la lámina son de neón sobre oscuro, y los dos botones de acción son claros:
   ahí el trazo pierde el contraste que tiene en el resto (el "VS" rojo sobre el naranja es
   el peor). Se les da un contorno oscuro en vez de repintarlos — misma idea que con los
   botones: separar con luz, no cambiar el dibujo. */
.nav-play::before,
.nav-mp::before {
  filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.9)) drop-shadow(0 1px 1px rgba(0, 0, 0, 0.55));
}

#play-ai-button::before { content: ''; background-image: url('/assets/sprites/ui/icon-vscpu.png'); }
#nav-multiplayer::before { content: ''; background-image: url('/assets/sprites/ui/icon-vsplayer.png'); }
#nav-practice::before { content: ''; background-image: url('/assets/sprites/ui/icon-training.png'); }
#nav-howtoplay::before { content: ''; background-image: url('/assets/sprites/ui/icon-help.png'); }
#nav-social::before { content: ''; background-image: url('/assets/sprites/ui/icon-friends.png'); }
#nav-rankings::before { content: ''; background-image: url('/assets/sprites/ui/icon-tournament.png'); }
#nav-store::before { content: ''; background-image: url('/assets/sprites/ui/icon-chest.png'); }
#nav-profile::before { content: ''; background-image: url('/assets/sprites/ui/icon-brain.png'); }
#play-mp-button::before { content: ''; background-image: url('/assets/sprites/ui/icon-energy.png'); }
#create-room-button::before { content: ''; background-image: url('/assets/sprites/ui/icon-key.png'); }
#controls-button::before,
#howtoplay-controls-button::before { content: ''; background-image: url('/assets/sprites/ui/icon-keyboard.png'); }
/* El mando, recortado de la MISMA fila de la lámina 08.2 que el teclado. Los dos accesos a la
   hoja de controles son hermanos en el menú y también en la lámina: mismo grosor de trazo,
   mismo neón, misma caja. La primera versión usaba la pieza del pad (`touch-btn.png`), que es
   vidrio gris translúcido: decía "táctil" pero rompía la familia de iconos del menú. */
#howtoplay-touch-button::before { content: ''; background-image: url('/assets/sprites/ui/icon-controller.png'); }
.menu-back::before,
#chat-back-button::before { content: ''; background-image: url('/assets/sprites/ui/icon-back.png'); }

/* La ✕ de cerrar la lámina de controles: era el único glifo que quedaba del sistema. */
.controls-close {
  background-image: url('/assets/sprites/ui/icon-close.png');
  background-repeat: no-repeat;
  background-position: center;
  background-size: 60% 60%;
}

/* Contenedor de panel: el panel maestro 9-slice de la lámina 10. Es lo que hace que un
   panel se lea como una ventana del juego y no como un bloque de texto sobre el video.
   El marco se come 28 px de alto útil, así que los paginadores reparten distinto — por eso
   hay que volver a medir los nueve paneles después de tocar esto, no solo mirarlos. */
/* ---- IDENTIDAD DE LA BARRA DEL MENÚ ----------------------------------------------------
   La barra lleva el bloque de perfil compartido (talla `menu`), el saldo y "Sign out".

   El reparto: el bloque toma el sitio sobrante y las monedas y "Sign out" se quedan con el
   suyo. Sin fijarlo, el navegador repartía a ojo — el título salía cortado a dos palabras y
   "Sign out" se partía en dos renglones. */
.identity-bar { flex-wrap: nowrap; }
.identity-bar .identity-wallet,
.identity-bar #switch-account {
  flex: none;
  white-space: nowrap;
}
/* Base 0, no `auto`: con `auto` la tira solo pide lo que ocupa su contenido y el sobrante de
   la barra se quedaba sin repartir, así que el título salía con puntos suspensivos teniendo
   sitio libre al lado. */
#identity-name {
  display: flex;
  align-items: center;
  flex: 1 1 0;
  min-width: 0;
}
#identity-name .bba-id { flex: 1 1 0; min-width: 0; }

/* La tira pasó de una línea a dos (el bloque de perfil apila nombre y título), así que el
   relleno vertical baja de 9 a 5 px para devolver lo que el avatar se llevó. Medido: la
   barra pasa de 44 a 46 px, no de 44 a 54. El alto del menú es un presupuesto cerrado —ver
   los comentarios de `.menu-shell`— y dos píxeles es lo que se puede gastar acá. */
.identity-bar { padding-top: 5px; padding-bottom: 5px; }

/* El marco del panel cuesta 26 px de alto (13 arriba y 13 abajo) y el escenario no los
   regala: medido, el contenido del menú pasaba a 560 px dentro de una caja de 548. No corta
   nada —la caja deja pasar lo que sobra— pero se come el aire del marco, y una entrada más
   en el menú sí cortaría. Se paga con espaciado, que es lo único que no cuesta contenido. */
.menu-shell { row-gap: 4px; }
.menu-shell .identity-bar { margin-top: 8px; }

.menu-shell .menu-panel,
.menu-shell .searching-panel {
  /* El interior de la pieza es vidrio semitransparente, y detrás corre el video del menú:
     sin respaldo el fondo se cuela bajo el texto. El color va DEBAJO del arte (border-image
     se pinta sobre el background), así que el vidrio se conserva y solo se apaga lo que hay
     más atrás. Es la transparencia haciendo su trabajo, no tapándolo.

     `padding-box` es lo que impide que ese respaldo se vea como un rectángulo. Un fondo
     llena por defecto hasta el borde EXTERNO, o sea también los cuatro ángulos que el marco
     deja cortados en diagonal: asomaban como esquinas cuadradas por fuera de la silueta.
     Recortándolo al borde interno, el respaldo queda dentro del marco y la silueta del panel
     vuelve a ser la del dibujo. Mismo defecto que el rectángulo de los botones, otra causa. */
  background: rgba(5, 7, 18, 0.55);
  background-clip: padding-box;
  /* 13 y no 14: el último par de píxeles es exactamente lo que separaba a #menu-status de
     quedar fuera del escenario. El marco se paga en alto útil y hay que pagarlo con algo. */
  border: 13px solid transparent;
  border-image: url('/assets/sprites/ui/panel.png') 24 fill / 13px / 0 stretch;
}

/* Filas de tabla y de lista: la pieza de la lámina 04. Sirve a las cuatro listas del menú
   porque todas son lo mismo — una fila con contenido a izquierda y a derecha. */
.friend-row,
.achievement,
.store-item,
.identity-card {
  /* Mismo recorte al borde interno que en el panel: si no, el respaldo asoma en cuadrado por
     fuera de los ángulos cortados de cada fila. */
  background: rgba(4, 6, 16, 0.5);
  background-clip: padding-box;
  border: 9px solid transparent;
  border-image: url('/assets/sprites/ui/row.png') 15 fill / 9px / 2px stretch;
  transition: filter 0.13s ease-out;
  border-radius: 0;
  padding-top: 0;
  padding-bottom: 0;
}
.friend-row { padding-left: 6px; padding-right: 6px; }
.achievement { padding-left: 4px; padding-right: 4px; }
.store-item { padding-left: 4px; padding-right: 4px; }
.identity-card { padding-left: 8px; padding-right: 8px; }
/* Misma luz que los botones: halo hacia afuera, dibujo intacto. */
.friend-row,
.store-item {
  --glow-cerca: rgba(9, 189, 219, 0.7);
  --glow-lejos: rgba(124, 56, 216, 0.38);
}
.friend-row:hover,
.store-item:hover {
  filter:
    drop-shadow(0 0 2px var(--glow-cerca))
    drop-shadow(0 0 7px var(--glow-cerca))
    drop-shadow(0 0 17px var(--glow-lejos));
}
/* La tarjeta premium NO cambia de pieza, solo de luz. Podría llevar un marco reteñido como
   el botón, pero ese canal ya está ocupado: `.store-item.equipped` de acá abajo cambia el
   marco, y una tarjeta premium YA COMPRADA Y EQUIPADA tendría que ser las dos cosas a la
   vez. Premium se dice por el rótulo `· premium`, por la gema del precio y por este halo;
   el marco queda reservado para "lo tenés puesto", que es un estado y no una categoría. */
.store-item-premium {
  --glow-cerca: rgba(176, 64, 255, 0.75);
  --glow-lejos: rgba(224, 160, 255, 0.4);
}

.store-item.equipped,
.achievement.unlocked {
  border-image-source: url('/assets/sprites/ui/row-selected.png');
  border-image-slice: 15 fill;
}

/* ---- CAMPOS DE TEXTO Y DESPLEGABLES ----------------------------------------------------

   Lo último que quedaba del lenguaje anterior al arte eran los CAMPOS. Botones, paneles,
   filas y pestañas ya llevaban su pieza; los sitios donde se escribe seguían siendo el
   recuadro de formulario web de siempre — `1px solid #2a2f4a` con esquinas redondeadas — y
   se notaba justo donde un campo toca un botón: el "CODE" de sala privada al lado de un
   JOIN de neón, o los tres campos del registro encima de un CREATE ACCOUNT verde.

   La pieza es `row.png`, la misma de las filas de lista, y no es una elección de repuesto:
   una fila y un campo son el mismo dibujo —un hueco hundido dentro del panel— y la pieza ya
   está calibrada para eso. Con la de BOTÓN un campo diría "apretame" en vez de "escribí acá",
   que es exactamente el error que este arte evita en todos lados: la pieza dice qué es la
   cosa, no solo de qué juego es.

   EL MARCO SE PAGA EN ALTO, y en la pantalla de cuentas el presupuesto está cerrado: no hay
   paginador ni desplazamiento que rescate lo que se salga (regla del escenario, GDD §4.7).
   Por eso cada regla devuelve del relleno lo que el borde se lleva, y ninguna cambia el alto
   más de un par de píxeles. Medido con devtools/shot-paneles.js antes y después. */
.auth-panel input[type="email"],
.auth-panel input[type="password"],
.auth-panel input[type="date"],
.auth-panel input:not([type]),
.text-input,
.code-input {
  /* (Acá iban también `.geo-picker .geo-select` y `.geo-selects select`. NO ESTÁN PORQUE NO
     EXISTEN: el selector de ubicación dejó de usar desplegables nativos cuando pasó a abrir
     el panel con corredera, y hoy sus filas son `.geo-pick`/`.geo-country`, que son
     `.nav-button` y ya visten la pieza de botón. Las reglas viejas de `.geo-select` siguen
     escritas más arriba en esta hoja y tampoco alcanzan a nadie — no hay un solo `<select>`
     en todo el cliente.) */
  /* Mismo recorte al borde interno que en paneles y filas: sin él, el respaldo asoma en
     cuadrado por fuera de los cuatro ángulos que el marco deja cortados en diagonal. */
  background: rgba(4, 6, 16, 0.5);
  background-clip: padding-box;
  border: 9px solid transparent;
  border-image: url('/assets/sprites/ui/row.png') 15 fill / 9px / 2px stretch;
  border-radius: 0;
  color: #f0f2ff;
  outline: none;
}
/* ALTO FIJO Y EL MISMO PARA LOS CUATRO, no un relleno que dé la casualidad de coincidir.
   Un `type="date"` no se mide como los demás: el navegador le arma dentro tres campitos y un
   botón de calendario, y con el mismo relleno que sus vecinos salía 5,2 px más alto — que es
   poco, pero es visible cuando los cuatro están apilados y solo uno sobresale. Declarado el
   alto, son idénticos por construcción y no por coincidencia, y ninguna diferencia futura
   entre navegadores los puede desalinear.
   34 px con `box-sizing: border-box`: el marco se come hacia adentro, así que el campo mide
   34 lleve el borde que lleve. Antes medía 36,2. */
.auth-panel input[type="email"],
.auth-panel input[type="password"],
.auth-panel input[type="date"],
.auth-panel input:not([type]) {
  height: 34px;
  padding: 0 8px;
}
.text-input, .code-input { padding: 1px 8px; }

/* Enfocado: la misma luz que reciben los botones, no otro borde. Cambiar el color del marco
   obligaría a otra pieza; el halo nace del alfa del dibujo y se pinta hacia afuera, así que
   el campo no se mueve ni un píxel al recibir el cursor. */
.auth-panel input:focus,
.text-input:focus,
.code-input:focus {
  border-color: transparent;
  filter:
    drop-shadow(0 0 2px rgba(9, 189, 219, 0.8))
    drop-shadow(0 0 7px rgba(9, 189, 219, 0.45));
}

/* ---- FECHA DE NACIMIENTO ---------------------------------------------------------------

   Era el único campo del juego que ni siquiera llevaba el recuadro anterior. La lista de
   selectores de `.auth-panel` nombraba email, password y los campos sin tipo, y `type="date"`
   no es ninguno de los tres: lo dibujaba entero el sistema. Medido, salía con fondo BLANCO
   (rgb 255,255,255), borde gris de Windows de 2 px y 29 px de alto contra los 50 de los
   campos de arriba y de abajo — un trozo de otra aplicación pegado en medio del panel.

   El calendario que se abre al apretar el icono NO se toca: ese sistema funciona bien y es el
   que la persona ya sabe usar. Lo único que cambia es de qué color lo pinta el navegador, y
   eso ya está resuelto en la raíz con `color-scheme: dark`. */
.auth-panel input[type="date"] { font-size: 0.95rem; }

/* El navegador dibuja la fecha en piezas sueltas (día, mes, año) y ninguna hereda el `color`
   del campo por su cuenta: hay que nombrarlas. El relleno propio se apaga porque ya lo pone
   la regla del campo, y sumado corría el texto contra el marco. */
.auth-panel input[type="date"]::-webkit-datetime-edit { color: #f0f2ff; padding: 0; }

/* VACÍO SE LEE COMO UN PLACEHOLDER, igual que los tres campos de arriba. El estado sale de la
   validación y no de JavaScript: el campo lleva `required` (ver el porqué en index.html), así
   que sin fecha es `:invalid`. Mientras se escribe también lo es, o sea que la fecha se
   enciende recién cuando está completa — que es justo cuando pasa a decir algo. */
.auth-panel input[type="date"]:invalid::-webkit-datetime-edit { color: #6a72a0; }

/* El icono del calendario viene en gris del sistema. Se TIÑE al cian del panel en vez de
   reemplazarlo por un dibujo propio: la lámina de dirección de arte no trae pieza de
   calendario, y un icono inventado desentonaría más que este teñido.
   `brightness(0)` primero lo lleva a negro puro, y recién desde ahí la cadena lo sube al
   color: partiendo del gris original el resultado dependería del tono con que cada navegador
   dibuje el glifo. */
.auth-panel input[type="date"]::-webkit-calendar-picker-indicator {
  filter: brightness(0) saturate(100%) invert(78%) sepia(51%) saturate(1587%)
          hue-rotate(166deg) brightness(103%) contrast(101%);
  opacity: 0.72;
  cursor: pointer;
}
.auth-panel input[type="date"]::-webkit-calendar-picker-indicator:hover { opacity: 1; }

/* ---- LAS DOS FAMILIAS DE BOTONES QUE SE HABÍAN QUEDADO FUERA ---------------------------

   `.auth-tab` y `.rank-view` SON pestañas —hacen lo mismo que `.panel-tab`, que ya viste la
   pieza— y solo se llamaban distinto, así que se les da el mismo tratamiento entero: pieza
   de botón, `row-selected.png` para la activa y el halo cian→magenta de lo elegido. Sin esto
   la pantalla de cuentas tenía sus tres pestañas planas encima de un formulario de neón.

   Los tres proveedores (Google, Apple, Play Store) están apagados a propósito y siguen
   estándolo: reciben la pieza y encima el tratamiento medido del desactivado, el mismo de
   todos los botones apagados del juego. Un bloque plano y gris se lee como relleno sin
   terminar; uno desteñido con la forma del resto se lee como lo que es, un botón que todavía
   no está. */
.auth-tab,
.rank-view,
.rank-descend,
.provider-button,
.controls-close,
.chat-invite-button {
  background: none;
  border: 8px solid transparent;
  border-image: url('/assets/sprites/ui/button-secondary.png') 16 fill / 8px / 3px stretch;
  border-radius: 0;
  box-shadow: none;
  --glow-cerca: rgba(124, 56, 216, 0.8);
  --glow-lejos: rgba(9, 189, 219, 0.42);
  transition: filter 0.14s ease-out, transform 0.07s ease-out;
}
/* MEDIDO, y por eso son estos números y no los del resto de la familia. El marco cuesta
   ancho además de alto: con 8 px por lado la pestaña perdía 12 px de sitio para el texto,
   "Create Account" partía en dos renglones y la caja entera crecía 18 px — lo suficiente para
   que el pie del formulario se saliera del escenario. Son tres pestañas de 125 px que tienen
   que decir tres cosas de largo muy distinto, así que el marco baja a 6 px y la letra a
   0,72 rem: con eso la más larga entra en un renglón con 19 px de sobra.

   `nowrap` es una red, no una solución: si alguna vez un rótulo no entra, el defecto tiene
   que verse en el propio botón y no convertirse en un panel 18 px más alto que empuja el
   contenido de abajo fuera del escenario, donde ya no hay forma de alcanzarlo (GDD §4.7). */
.auth-tab {
  border-width: 6px;
  border-image-width: 6px;
  /* 4 px verticales y no 1: con el marco ya resuelto sobran 12 px en la caja, y devolvérselos
     a las pestañas las deja en los 30 px que medían antes. Una pestaña de 24 px se lee como
     un control secundario, y estas tres son la primera decisión de la pantalla. */
  padding: 4px 3px;
  font-size: 0.72rem;
  white-space: nowrap;
}
/* Sin relleno vertical: 9 px de marco por lado ya son más que los 10 que tenía en total, así
   que el botón queda 3 px más bajo que antes en vez de 8 más alto. */
.provider-button { border-width: 9px; border-image-width: 9px; padding: 0 4px; opacity: 1; }
/* Los dos de rankings son diminutos —22 y 26 px de alto— y viven en un panel medido al
   píxel: con 8 px de marco arriba y abajo el texto se quedaba sin sitio y la fila crecía. */
.rank-view { border-width: 5px; border-image-width: 5px; padding: 0 8px; }
.rank-descend { border-width: 6px; border-image-width: 6px; padding: 0 5px; }
/* La ✕ tiene tamaño fijo (34x34) y `box-sizing: border-box`, así que el marco se come hacia
   adentro y el botón no cambia de medida.
   EL ICONO HAY QUE VOLVER A PONERLO: la regla de arriba lleva `background: none`, y ese atajo
   no apaga solo el color — borra también la imagen que le puso `.controls-close` unas líneas
   antes. Sin esta línea el botón queda con marco y sin ✕, que es peor que como estaba.

   Y EN PÍXELES, NO EN PORCENTAJE: un `background-size` en % se mide contra la caja de
   relleno, que el marco acaba de encoger de 32 a 20 px — el 60% de antes dejaba la ✕ en
   10 px, la mitad de lo que se veía. Con una medida absoluta el icono no depende del grosor
   del marco. */
.controls-close {
  border-width: 6px;
  border-image-width: 6px;
  background: url('/assets/sprites/ui/icon-close.png') center / 15px 15px no-repeat;
}
/* La invitación dentro de un mensaje: verde porque es aceptar, o sea la MISMA pieza que
   "Play vs CPU" y "Create Account". El color ya venía diciendo eso; ahora lo dice el dibujo. */
.chat-invite-button {
  border-image-source: url('/assets/sprites/ui/button-primary.png');
  border-width: 7px;
  border-image-width: 7px;
  padding: 2px 8px;
  color: #04182a;
  --glow-cerca: rgba(101, 241, 121, 0.8);
  --glow-lejos: rgba(9, 189, 219, 0.45);
}

.auth-tab:hover,
.rank-view:hover,
.rank-descend:hover,
.controls-close:hover,
.chat-invite-button:hover {
  background: none;
  border-color: transparent;
  filter:
    drop-shadow(0 0 2px var(--glow-cerca))
    drop-shadow(0 0 7px var(--glow-cerca))
    drop-shadow(0 0 17px var(--glow-lejos));
}
.auth-tab:active,
.rank-view:active,
.rank-descend:active,
.controls-close:active,
.chat-invite-button:active {
  filter: brightness(0.8) saturate(0.94) contrast(1.06);
  transform: translateY(1px);
}

/* Lo elegido, con la misma pieza y el mismo halo que `.panel-tab.active`. */
.auth-tab.active,
.rank-view.active {
  border-image-source: url('/assets/sprites/ui/row-selected.png');
  border-image-slice: 15 fill;
  background: none;
  color: #f2f3ff;
  --glow-cerca: rgba(9, 189, 219, 0.8);
  --glow-lejos: rgba(221, 33, 181, 0.45);
}

/* Los tres números del desactivado son los medidos en la lámina, los mismos que usan los
   demás botones apagados. `:disabled:hover` está para apagar el halo: sin él un botón que no
   hace nada se ilumina al pasarle el ratón, que es lo contrario de lo que tiene que decir. */
.provider-button:disabled,
.provider-button:disabled:hover,
.rank-view[disabled],
.rank-view[disabled]:hover {
  filter: grayscale(0.68) brightness(0.72) contrast(0.66);
  opacity: 1;
  transform: none;
  cursor: not-allowed;
}

/* ---- CAJAS QUE SEGUÍAN SIENDO RECUADROS ------------------------------------------------

   La caja de la pantalla de cuentas es un PANEL: contiene un formulario entero, tiene
   pestañas y un botón de acción, exactamente como los nueve del menú. Llevaba el recuadro de
   1 px con esquinas redondeadas, así que la primera pantalla que ve un jugador nuevo era la
   única del juego sin el marco de la lámina 10. El relleno baja 12 px por lado para devolver
   lo que se lleva el marco. */
.auth-box {
  background: rgba(5, 7, 18, 0.55);
  background-clip: padding-box;
  border: 13px solid transparent;
  border-image: url('/assets/sprites/ui/panel.png') 24 fill / 13px / 0 stretch;
  border-radius: 0;
  padding: 4px 8px 2px;
}

/* Bloques hundidos dentro de un panel: la misma pieza de fila que ya visten las tarjetas de
   perfil, los logros y los artículos de la tienda. La barra de identidad era el caso más
   raro de todos — llevaba dentro una `.identity-card` CON marco de arte, o sea el mismo
   contenido dibujado en dos lenguajes, uno adentro del otro. */
.identity-bar,
.profile-stat,
.chat-messages {
  background: rgba(4, 6, 16, 0.5);
  background-clip: padding-box;
  border: 9px solid transparent;
  border-image: url('/assets/sprites/ui/row.png') 15 fill / 9px / 2px stretch;
  border-radius: 0;
}
/* 6 px y no 9: el alto de la barra de identidad es un presupuesto cerrado —ver el comentario
   de `.menu-shell`, donde dos píxeles son lo que se puede gastar—. Con 6 px de marco y cero
   relleno vertical mide EXACTAMENTE lo que medía con 1 px de borde y 5 de relleno. */
.identity-bar {
  border-width: 6px;
  border-image-width: 6px;
  padding: 0 10px;
}
.profile-stat { padding: 2px 0; }
/* `height: 300px` con `box-sizing: border-box`: el marco se come hacia adentro y la caja del
   chat no cambia de tamaño. El relleno baja de 10 a 1 para que el marco no muerda el texto. */
.chat-messages { padding: 1px; }

/* El código de sala usa la pieza de SELECCIONADO y no la de fila normal, y es por lo mismo
   que la llevan un logro desbloqueado o el artículo equipado: no es una caja más de la
   pantalla, es la única que hay que leer y dictar. Ese realce lo decía un borde cian suelto;
   ahora lo dice la pieza que en este juego ya significa "esta". */
.room-code-box {
  background: rgba(4, 6, 16, 0.5);
  background-clip: padding-box;
  border: 9px solid transparent;
  border-image: url('/assets/sprites/ui/row-selected.png') 15 fill / 9px / 2px stretch;
  border-radius: 0;
  padding: 2px 14px;
}

/* ==== Controles táctiles (pad estilo Super Nintendo) ==================================
   Ver client/src/touchControls.js para el porqué de la disposición y del reparto de
   botones. Acá solo importan dos decisiones de estilo:

   1. El pad NO se escala con el escenario. Todo lo demás del juego vive dentro de
      `.screen`, que tiene `transform: scale(var(--stage-scale))`, y en un teléfono ese
      factor es ~0.68: un botón de 52px aterrizaría en 35px reales, por debajo del mínimo
      táctil de 44px (iOS) / 48dp (Android). Por eso está en `position: fixed` a nivel de
      `body` y sus medidas son píxeles físicos. Es la única excepción deliberada a la regla
      de escalado del juego, y es porque el dedo es una medida física, no una proporción.

   2. Solo los controles reciben toques. La raíz cubre la pantalla para poder capturar el
      puntero cuando el dedo se sale del pad, pero pasa los toques de largo; únicamente la
      cruz y los botones son blanco. Si no, el pad bloquearía los clics del propio juego. */

#bba-touch {
  position: fixed;
  inset: 0;
  z-index: 60;
  pointer-events: none;
  touch-action: none;
  -webkit-user-select: none;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
}
#bba-touch.oculto { display: none; }

/* Las dos piezas, del mismo tamaño y a la misma distancia del borde. Las medidas las escribe
   `touchControls.js` como variables (ver ahí el porqué de cada número); acá solo se reparten. */
#bba-touch .bba-cruz,
#bba-touch .bba-diamante {
  position: absolute;
  bottom: var(--pad-margen, 12px);
  width: var(--pad-lado, 154px);
  height: var(--pad-lado, 154px);
}
#bba-touch .bba-cruz { left: var(--pad-margen, 12px); pointer-events: auto; touch-action: none; }
#bba-touch .bba-diamante { right: var(--pad-margen, 12px); }

/* Las piezas internas de la cruz no son blanco de toques: el blanco es la cruz entera, y
   qué dirección se apretó lo decide la geometría (tercios). Es lo que permite deslizar el
   pulgar de una dirección a otra sin que quede una tecla pegada. */
#bba-touch .bba-cruz > * { pointer-events: none; }

/* ---- Las piezas salen de la lámina de dirección de arte -------------------------------
   assets/sprites/MATERIAPRIMA-art-direction-and-samples/touchHUD.png, recortadas con
   `node devtools/art/extract.js`. NO se editan a mano: manda el recorte declarado en ese
   archivo, y volver a correrlo pisa el PNG.

   La lámina se titula «neutral translucent gray» y trae su propia referencia de opacidad
   (40-70%): de ahí sale el valor de --pad-opacidad. El vidrio de las piezas ya viene
   semitransparente por el alfa recuperado de la lámina, así que esta opacidad se suma a esa.

   Y trae los DOS estados dibujados, con la flecha invertida en el apretado (clara sobre
   vidrio oscuro pasa a oscura sobre vidrio encendido). Por eso acá el estado sí cambia de
   pieza en vez de resolverse con un filtro de luz como en el resto del menú: aclarar el
   botón aclararía también la flecha. Ver la justificación medida en devtools/art/extract.js. */
#bba-touch {
  /* MÁS TRANSPARENTE DESDE EL 2026-08-16 (pedido del dueño, junto con el achique). Baja de
     0.78 a 0.64: lo justo para que lo que pasa por debajo se siga leyendo sin que el pad deje
     de encontrarse con el pulgar. No es lo que resuelve la oclusión —de eso se encargan el
     achique y la barra plana—, es lo que hace tolerable la que queda. */
  --pad-opacidad: 0.64;
  /* Proporciones tomadas de la lámina, no elegidas a ojo. Medida ahí: octógono 128 px,
     círculo central 90 px, octógono 128 px = 346 px de cruz. Eso da un octógono de 0.369 del
     lado de la cruz y un círculo de 0.262, y así se escriben: como fracciones del lado, para
     que sigan siendo las de la lámina cuando el lado cambie.

     Lo importante es que en la lámina NO hay hueco: el círculo del medio LLENA el espacio
     entre los octógonos, y por eso la cruz se lee como una sola pieza. Dibujando los
     octógonos más chicos que su celda quedaban cuatro botones flotando, porque sobre el juego
     —fondo oscuro— ese círculo oscuro no se ve, mientras que en la lámina se apoya en el
     ajedrez de transparencia y sí.

     El octógono es MAYOR que la celda táctil (0.369 del lado contra un tercio): en la lámina
     los octógonos se anidan por sus esquinas cortadas. Se puede porque el elemento del dibujo
     tiene `pointer-events: none` y el reparto de zonas es geométrico (tercios de la cruz), así
     que el tamaño del dibujo no toca el área sensible. Cada dibujo lleva su propio elemento del
     mismo tamaño que la imagen: un fondo no desborda su caja, y ese fue el defecto anterior
     —58 px de imagen en una caja de 52 se recortaba y el octógono parecía un cuadrado. */
  --pad-celda: calc(var(--pad-lado, 154px) / 3);
  --pad-octogono: calc(var(--pad-lado, 154px) * 0.369);
}
#bba-touch .bba-cruz,
#bba-touch .bba-diamante { opacity: var(--pad-opacidad); }

/* Cada dibujo, CENTRADO EN EL CENTRO DE SU CELDA, con el elemento del mismo tamaño que la
   imagen para que no se recorte. Los centros de las tres celdas están en 0,5 · 1,5 y 2,5 celdas,
   y de ahí se resta media imagen — así el anidado por las esquinas (el dibujo asoma un poco
   fuera de la cruz) sale solo de la proporción y no de tres números escritos a mano, que era
   como estaba y por eso había que reescribirlos cada vez que cambiaba el tamaño. */
#bba-touch .bba-flecha {
  position: absolute;
  width: var(--pad-octogono);
  height: var(--pad-octogono);
  background-repeat: no-repeat;
  background-position: center;
  background-size: var(--pad-octogono) var(--pad-octogono);
  transition: transform 90ms linear;
  --pad-cerca: calc(var(--pad-celda) * 0.5 - var(--pad-octogono) / 2);
  --pad-medio: calc(var(--pad-celda) * 1.5 - var(--pad-octogono) / 2);
  --pad-lejos: calc(var(--pad-celda) * 2.5 - var(--pad-octogono) / 2);
}
#bba-touch .bba-f-up { left: var(--pad-medio); top: var(--pad-cerca); background-image: url('/assets/sprites/ui/touch-up.png'); }
#bba-touch .bba-f-left { left: var(--pad-cerca); top: var(--pad-medio); background-image: url('/assets/sprites/ui/touch-left.png'); }
#bba-touch .bba-f-right { left: var(--pad-lejos); top: var(--pad-medio); background-image: url('/assets/sprites/ui/touch-right.png'); }
#bba-touch .bba-f-down { left: var(--pad-medio); top: var(--pad-lejos); background-image: url('/assets/sprites/ui/touch-down.png'); }

#bba-touch .bba-f-up.bba-activo { background-image: url('/assets/sprites/ui/touch-up-on.png'); }
#bba-touch .bba-f-left.bba-activo { background-image: url('/assets/sprites/ui/touch-left-on.png'); }
#bba-touch .bba-f-right.bba-activo { background-image: url('/assets/sprites/ui/touch-right-on.png'); }
#bba-touch .bba-f-down.bba-activo { background-image: url('/assets/sprites/ui/touch-down-on.png'); }
#bba-touch .bba-flecha.bba-activo { transform: scale(0.96); }

/* Círculo del medio: en la lámina es lo que CIERRA la cruz — llena el espacio entre los
   cuatro octógonos y marca el reposo del pulgar. 44 px es la medida de la lámina llevada a
   escala. Coincide con la zona muerta de la rejilla 3x3, así que apoyar el pulgar ahí no
   manda ninguna dirección. */
#bba-touch .bba-centro {
  position: absolute;
  left: 50%;
  top: 50%;
  width: calc(var(--pad-lado, 154px) * 0.262);
  height: calc(var(--pad-lado, 154px) * 0.262);
  margin: calc(var(--pad-lado, 154px) * -0.131) 0 0 calc(var(--pad-lado, 154px) * -0.131);
  border-radius: 50%;
  background: rgba(8, 10, 20, 0.78);
  box-shadow: inset 0 0 0 1px rgba(232, 234, 245, 0.14);
}

/* Placa del diamante: cuadrado girado 45°, como en la lámina. Bien tenue — su trabajo es
   agrupar los cuatro botones, no taparle el juego a nadie. */
#bba-touch .bba-placa {
  position: absolute;
  left: 50%;
  top: 50%;
  width: calc(var(--pad-lado, 154px) * 0.729);
  height: calc(var(--pad-lado, 154px) * 0.729);
  margin: calc(var(--pad-lado, 154px) * -0.3645) 0 0 calc(var(--pad-lado, 154px) * -0.3645);
  border-radius: calc(var(--pad-lado, 154px) * 0.141);
  transform: rotate(45deg);
  background: rgba(10, 13, 26, 0.42);
}

/* EL RÓTULO NO ENCOGE CON EL BOTÓN. El círculo bajó de 58 a 48 px, y aplicarle esa misma
   proporción al texto lo habría dejado en 7,9 px — ilegible de un vistazo, que es la única
   forma en que se lee un rótulo mientras se juega. La letra baja lo mínimo (9,5 → 9) y el
   glifo del emote otro tanto: «ACTION», que es el rótulo más largo, mide 38 px a ese cuerpo y
   entra holgado en 48. Achicar el control no puede significar achicar lo que dice. */
#bba-touch .bba-btn {
  position: absolute;
  width: var(--pad-boton, 53px);
  height: var(--pad-boton, 53px);
  pointer-events: auto;
  touch-action: none;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  font-size: 9px;
  font-weight: 800;
  letter-spacing: 0.3px;
  color: rgba(255, 255, 255, 0.94);
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.85);
  background-repeat: no-repeat;
  background-position: center;
  background-size: var(--pad-boton, 53px) var(--pad-boton, 53px);
  background-image: url('/assets/sprites/ui/touch-btn.png');
  transition: transform 90ms linear;
}
/* Apretado: la pieza encendida de la lámina, y el rótulo se oscurece para seguir a la
   flecha invertida del D-pad (sobre vidrio claro, texto blanco desaparece). */
/* SIN `transform` EN EL BOTÓN: el botón ES el blanco del toque.
   `entradasEn()` resuelve qué se apretó con `document.elementFromPoint()`, y esa función
   respeta las transformaciones CSS — o sea que mientras el botón se encogía durante los 90 ms
   de la transición, un dedo apoyado cerca del borde quedaba FUERA de él a mitad de camino. El
   pad leía «ya no está tocando nada», soltaba la tecla, y al terminar la animación la volvía a
   apretar. Es el «se pega y se devuelve» descrito por el dueño, y ocurría dentro de la propia
   animación de presionarse.
   El aviso de que está apretado no se pierde: la pieza encendida de la lámina ya lo dice, y el
   rótulo se sigue hundiendo — pero lo hace un HIJO, que no recibe toques. */
#bba-touch .bba-btn.bba-activo {
  background-image: url('/assets/sprites/ui/touch-btn-on.png');
  color: rgba(14, 16, 28, 0.92);
  text-shadow: none;
}
#bba-touch .bba-btn.bba-activo .bba-glifo,
#bba-touch .bba-btn.bba-activo .bba-rotulo { transform: scale(0.94); }
#bba-touch .bba-glifo,
#bba-touch .bba-rotulo { transition: transform 90ms linear; }
#bba-touch .bba-glifo { font-size: 15px; line-height: 1; margin-bottom: 1px; }

@media (prefers-reduced-motion: reduce) {
  #bba-touch .bba-btn,
  #bba-touch .bba-flecha,
  #bba-touch .bba-glifo,
  #bba-touch .bba-rotulo { transition: none; }
  #bba-touch .bba-flecha.bba-activo { transform: none; }
  #bba-touch .bba-btn.bba-activo .bba-glifo,
  #bba-touch .bba-btn.bba-activo .bba-rotulo { transform: none; }
}

/* ---- LA PANTALLA DE JUEGO CUANDO LA ENTRADA ES TÁCTIL ---------------------------------
   TODO lo de acá abajo cuelga de `body.bba-tactil`, que solo se pone tras un toque real:
   **con teclado la pantalla queda exactamente como estaba** (pedido del dueño, 2026-08-16:
   *"la UI de PC no debe ser alterada en ningún aspecto; funciona bien"*).

   POR QUÉ EL HUD VUELVE AL FLUJO, DESPUÉS DE HABERLO SACADO. Entre agosto y esta fecha el HUD
   flotaba sobre el campo, y la razón era buena: en un teléfono apaisado la altura es el recurso
   escaso, y con el HUD ocupando flujo el lienzo quedaba en el 35,8 % de la pantalla. Sacarlo lo
   subió al 74 %.

   Lo que esa medición no miraba es **cuánto de ese 74 % se podía ver**. Medido el 2026-08-16 en
   un teléfono de 844×390, en partida:

     el HUD flotante tapaba el 18,8 % del lienzo · la cruz el 6,1 % · los botones el 6,3 %
     TOTAL TAPADO: 31,2 %

   Y el 18,8 % del HUD es **el mismo en los tres aparatos que se probaron** (teléfono, Android
   grande, tablet), porque escala junto con el escenario; el pad, que va en píxeles físicos, cae
   del 12,4 % al 4,3 % según el aparato. O sea que el HUD era el que más tapaba y el único que
   tapaba igual en todos lados.

   La salida no es elegir entre «ocupa altura» y «tapa»: es que **deje de costar altura**. Un
   panel apilado —identidad, después los puntajes, después las estadísticas— mide 125 px a
   escala 1. Los mismos datos en UNA fila miden un tercio de eso, y ahí el lienzo puede volver a
   compartir la pantalla con él sin perder casi nada: pierde el alto de la barra y gana el
   18,8 % que ya no está tapado.

   El lienzo se achica solo y sin tocarlo: es `flex: 1 1 0` con `aspect-ratio`, así que toma el
   alto que sobra y deriva el ancho — se encoge en los dos ejes por igual y sigue centrado, que
   es exactamente lo que se pidió. */

/* La barra ocupa el ancho que haya, no los 800 px del bloque original: en una fila hacen falta
   tres veces más de horizontal que apilado, y ahí sí sobra.

   SIN RESERVA LATERAL PARA EL BOTÓN DE PANTALLA COMPLETA, y esa decisión se tomó midiendo. El
   botón va fijo en (10,10) en píxeles de pantalla y ocupa hasta el 56, así que la primera
   versión le reservó un hueco a cada lado del HUD. Costaba 104 px de escenario — y el ancho es
   justamente lo que falta: con esa reserva el nombre del jugador se quedaba en cuatro
   caracteres. Mover el botón un renglón hacia abajo (ver `.fs-btn` en el bloque táctil) cuesta
   cero y devuelve los 104 enteros. **Cuando dos cosas se pelean por el mismo sitio, conviene
   preguntarse cuál de las dos es la que puede moverse gratis.** */
body.bba-tactil #hud-overlay {
  max-width: none;
}

/* El botón de pantalla completa baja bajo la barra. Sigue en la esquina superior izquierda —el
   rincón que ningún pulgar tapa— y sigue siendo lo primero que se busca; lo único que cambia es
   que ya no comparte renglón con el HUD. A 52 px queda por debajo de la barra en el caso más
   alto medido (42 px en tablet) y sobre el margen izquierdo del lienzo, nunca encima del campo. */
body.bba-tactil .fs-btn { top: 52px; }
body.bba-tactil .controls-hint { display: none; }

body.bba-tactil .hud-panels {
  gap: 6px;
  margin-bottom: 4px;
  align-items: stretch;
}

/* ---- EL PANEL, EN DOS FILAS (2026-08-16, segunda pasada) ------------------------------
   La primera versión metió todo en UNA fila y funcionaba, pero con un costo que el dueño
   señaló enseguida: **el nombre y las cifras competían por el mismo ancho**, así que cuanto
   más crecía el puntaje menos nombre quedaba. Un dato que desaparece cuando el juego va bien
   es un dato mal colocado.
   Con dos filas dejan de competir: arriba el nombre, con todo el ancho del panel para él;
   abajo las cifras, que crecen sin desalojar a nadie. Y de paso vuelve lo que la fila única
   había obligado a esconder — la insignia de tipo de cuenta y el título equipado.

       ┌──────────────────────────────────────────┐
       │ ⬤⬤  [GUEST] Nombre del jugador  TÍTULO  │   ← fila 1: quién sos
       │ ⬤⬤  SCORE 40  GLOBAL 0   WALL gen 1     │   ← fila 2: cómo vas
       └──────────────────────────────────────────┘
         └── el avatar cruza las dos, y es el único

   POR QUÉ REJILLA Y NO DOS CAJAS APILADAS. El avatar tiene que cruzar las dos filas, y con
   cajas habría que sacarlo del bloque de identidad —romper `Identity.html`, que es la misma
   pieza en cinco vistas— o posicionarlo a mano. Con `display: contents` el bloque de identidad
   deja de generar caja y sus dos hijos (retrato y texto) pasan a ser celdas de ESTA rejilla,
   así que el avatar declara `grid-row: 1 / 3` y ya está. El HTML no se toca y el bloque
   compartido sigue intacto para el menú, el perfil, el panel social y el cartel VS. */
body.bba-tactil .hud-panel {
  display: grid;
  grid-template-columns: auto auto 1fr;
  align-content: center;
  column-gap: 8px;
  row-gap: 1px;
  padding: 3px 6px;
  border-radius: 8px;
  min-width: 0;
  /* Nada se dibuja fuera del panel. Es una red de seguridad y no la solución: si algún día un
     nombre o una cifra no entran, que queden cortados DENTRO de su marco. Sin esto el avatar de
     p2 se salía por el canto derecho y parecía un elemento suelto en la pantalla. */
  overflow: hidden;
}
body.bba-tactil .hud-panel > .bba-id { display: contents; }
body.bba-tactil .hud-panel .bba-id-pic { grid-area: 1 / 1 / 3 / 2; align-self: center; }
body.bba-tactil .hud-panel .bba-id-text { grid-area: 1 / 2 / 2 / 4; min-width: 0; }
body.bba-tactil .hud-panel-scores { grid-area: 2 / 2 / 3 / 3; }
body.bba-tactil .hud-panel-stats { grid-area: 2 / 3 / 3 / 4; }

/* El panel derecho es el mismo en espejo: las columnas se invierten y todo se alinea al canto
   opuesto. Se hace con columnas y no con `row-reverse` porque acá hay dos filas y el reflejo
   tiene que valer para las dos a la vez. Leído desde el avatar hacia adentro, los dos jugadores
   tienen sus datos en el mismo orden. */
body.bba-tactil .hud-panel-red { grid-template-columns: 1fr auto auto; justify-items: end; }
body.bba-tactil .hud-panel-red .bba-id-pic { grid-area: 1 / 3 / 3 / 4; }
body.bba-tactil .hud-panel-red .bba-id-text { grid-area: 1 / 1 / 2 / 3; justify-content: flex-end; }
body.bba-tactil .hud-panel-red .hud-panel-scores { grid-area: 2 / 2 / 3 / 3; }
body.bba-tactil .hud-panel-red .hud-panel-stats { grid-area: 2 / 1 / 3 / 2; }

/* La primera fila entera en una línea: insignia, nombre y título uno al lado del otro. El
   bloque de identidad los apila por diseño —ahí el avatar cubre justo esas dos líneas—, y acá
   quien cubre dos líneas es el avatar sobre las filas de la REJILLA, así que el texto se acuesta.
   Lo único que cede sigue siendo el nombre, que ya sabe terminar en puntos suspensivos: la
   insignia y el título son cortos y fijos, y las cifras viven en la otra fila. */
body.bba-tactil .hud-panel .bba-id-text { flex-direction: row; align-items: baseline; gap: 6px; }
body.bba-tactil .hud-panel .bba-id-title { min-height: 0; flex: none; }

/* El avatar sube a 36 px porque ahora cubre las dos filas de la rejilla, no las dos líneas del
   bloque de identidad: nombre (0.95rem·1.15) + hueco + cifras (1.1rem) ≈ 36. Es la misma cuenta
   que documentan las tallas de `.bba-id-*`, hecha sobre las filas de acá. */
body.bba-tactil .hud-panel .bba-id-hud {
  --id-avatar: 36px;
  --id-name: 0.95rem;
  --id-title: 0.58rem;
  --id-badge: 0.9rem;
  --id-gap: 8px;
}

/* Los puntajes y las estadísticas dejan de apilarse: etiqueta y valor uno al lado del otro,
   alineados por la base para que los números formen una línea. */
body.bba-tactil .hud-panel-scores { gap: 9px; margin-top: 0; align-items: baseline; }
body.bba-tactil .hud-score-block { flex-direction: row; align-items: baseline; gap: 3px; }
body.bba-tactil .hud-score-block-label { font-size: 0.55rem; letter-spacing: 0.04em; }
body.bba-tactil .hud-score-block-value { font-size: 1.1rem; }

/* Las estadísticas de la disciplina son como mucho DOS (Orbital Siege: casco e invaders;
   Sky Racer: oxígeno y combustible); las otras cinco traen una o ninguna. Por eso entran en la
   misma fila sin ningún caso especial: `min-width` era lo que las mantenía en columnas
   alineadas y acá estorba. */
body.bba-tactil .hud-panel-stats { flex-direction: row; gap: 9px; margin-top: 0; align-items: baseline; }
body.bba-tactil .hud-stat-row { min-width: 0; gap: 3px; }
body.bba-tactil .hud-stat-label { font-size: 0.55rem; letter-spacing: 0.03em; }
body.bba-tactil .hud-stat-value { font-size: 0.85rem; }

/* EL CENTRO: EL RELOJ AL MEDIO, Y A CADA LADO UN MARCADOR (pedido del dueño, 2026-08-16).
   A la izquierda el general —cuántas rondas lleva ganadas cada uno del campeonato— y a la
   derecha el de ESTA disciplina. Es una jerarquía espacial que se lee sin rótulos: lo de la
   izquierda dura toda la partida, lo de la derecha se reinicia cada ronda, y en el medio el
   único número que corre.

   El orden se hace con `order` y NO reordenando el HTML, porque en escritorio los tres van
   apilados en vertical y ahí el orden actual es el correcto — tocar el HTML cambiaría las dos
   vistas cuando solo hay que cambiar una. El `id` gana a la clase, que es lo que permite
   separar los dos bloques que comparten `hud-center-block`. */
body.bba-tactil .hud-center { flex-direction: row; align-items: center; gap: 9px; min-width: 0; align-self: center; }
body.bba-tactil .hud-center > .hud-center-block { order: 1; }
body.bba-tactil .hud-center > .hud-timer { order: 2; }
body.bba-tactil .hud-center > #hud-roundscore-block { order: 3; }
body.bba-tactil .hud-center-block { flex-direction: row; align-items: baseline; gap: 4px; }
body.bba-tactil .hud-caption { font-size: 0.52rem; letter-spacing: 0.05em; }
body.bba-tactil .hud-timer { font-size: 1.5rem; letter-spacing: 0; }
body.bba-tactil .hud-rounds { font-size: 1.05rem; }
body.bba-tactil .hud-roundscore { font-size: 1.1rem; }
body.bba-tactil .hud-rs-sep { margin: 0 3px; }

/* EL ANUNCIO DE RONDA SE ANCLA AL ESCENARIO, NO A LA BARRA. Se posiciona dentro de
   `#hud-overlay`, y con el overlay convertido en una franja de 40 px «40 % de su alto» son
   16 px: el cartel aparecería dentro de la barra en vez de sobre el campo. `fixed` dentro de
   `.screen` —que tiene `transform`, y por eso es el bloque contenedor— lo devuelve al centro
   del escenario, que es donde el jugador lo está mirando. */
body.bba-tactil .hud-banner {
  position: fixed;
  top: 44%;
}


