/* ==========================================================================
   Elige tú mismo · Product Universe
   Capa de experiencia del catálogo. El espacio lo forman los productos.

   Por qué esto es DOM y no WebGL
   ------------------------------
   Cada producto del universo es un <a> real con su texto dentro. Eso da
   gratis lo que una escena de canvas hay que reconstruir a mano y casi nunca
   se reconstruye bien: indexación, texto seleccionable, foco por teclado,
   lectores de pantalla, `loading="lazy"` nativo y funcionamiento sin
   JavaScript. La profundidad sale de `perspective` + `translate3d`, que el
   compositor resuelve en la GPU igual que un canvas.

   Las posiciones llegan desde el servidor en atributos `data-`, así que la
   escena aparece colocada antes de que ejecute una línea de JS. El JS añade
   cámara, reorganización animada, foco y comparación.
   ========================================================================== */

.universo-app {
  --esc-suelo: #0b1016;
  --esc-cielo: #182231;
  /* Objetos más pequeños que una tarjeta a propósito: caben más en pantalla
     —la sensación de abundancia es el objetivo— y dejan sitio para que un
     racimo se lea como varios productos y no como uno. Con 128 px, cinco
     máquinas de la misma banda de precio no caben en su franja y se tapaban
     entre ellas sin remedio. */
  --obj: 94px;
  --dur: .85s;
}

/* --- La escena ----------------------------------------------------------- */

.escena {
  position: relative;
  height: clamp(420px, 68vh, 660px);
  border-radius: var(--radio);
  overflow: hidden;
  background:
    radial-gradient(120% 80% at 50% 8%, #24344c 0%, var(--esc-cielo) 42%, var(--esc-suelo) 100%);
  perspective: 1150px;
  perspective-origin: 50% 42%;
  cursor: grab;
  touch-action: pan-y;
  contain: layout paint;
}
.escena.arrastrando { cursor: grabbing; }

/* La cuadrícula del suelo. Es lo que convierte «cosas flotando» en «un
   espacio con fondo»: sin una referencia de perspectiva el cerebro no lee
   profundidad, sólo tamaños distintos. */
.escena::before {
  content: "";
  position: absolute;
  left: -30%; right: -30%; bottom: -12%; height: 72%;
  background:
    repeating-linear-gradient(to right, rgba(255,255,255,.055) 0 1px, transparent 1px 84px),
    repeating-linear-gradient(to bottom, rgba(255,255,255,.055) 0 1px, transparent 1px 84px);
  transform: rotateX(72deg);
  transform-origin: 50% 100%;
  mask-image: linear-gradient(to top, #000 0%, transparent 78%);
  pointer-events: none;
}
/* Niebla de profundidad: apaga el fondo de la escena para que lo cercano
   destaque sin tener que agrandarlo tanto que tape el resto. */
.escena::after {
  content: "";
  position: absolute; inset: 0;
  background: radial-gradient(70% 55% at 50% 45%, transparent 38%, rgba(8,12,18,.72) 100%);
  pointer-events: none;
}

.camara {
  position: absolute;
  inset: 0;
  transform-style: preserve-3d;
  /* Sin esto, la mitad del catálogo no se puede pulsar.
     En un contexto `preserve-3d` el propio contenedor es un plano a z=0, y los
     hijos con translateZ NEGATIVA quedan DETRÁS de él. La cámara no pinta nada
     —no tiene fondo—, así que esos objetos se ven igual de bien; pero sí recibe
     los eventos de puntero, y se los queda. Ocho de los dieciséis productos
     eran perfectamente visibles e imposibles de pulsar, y `elementFromPoint`
     sobre ellos devolvía `div.camara`.
     Los objetos vuelven a activarlos abajo; el arrastre de la escena sigue
     funcionando porque sus escuchas viven en `.escena`, no aquí. */
  pointer-events: none;
  transform:
    translate3d(var(--cam-x, 0px), var(--cam-y, 0px), var(--cam-z, 0px))
    rotateX(var(--cam-rx, 0deg)) rotateY(var(--cam-ry, 0deg));
  transition: transform .5s var(--salida);
  will-change: transform;
}
.escena.arrastrando .camara,
.escena.inercia .camara { transition: none; }

/* --- El producto como objeto -------------------------------------------- */

.objeto {
  position: absolute;
  left: 0; top: 0;
  width: var(--obj);
  margin: 0;
  text-decoration: none;
  transform-style: preserve-3d;
  pointer-events: auto;   /* la cámara los desactiva; aquí se recuperan */
  transform:
    translate3d(calc(var(--px, 0) * 1px), calc(var(--py, 0) * 1px),
                calc((var(--pz, 0) + var(--pzx, 0)) * 1px))
    scale(var(--esc, 1));
  opacity: var(--op, 1);
  transition:
    transform var(--dur) var(--salida),
    opacity .45s ease,
    filter .45s ease;
  will-change: transform, opacity;
  z-index: var(--capa, 1);
}

/* El cuerpo del objeto. Sin tarjeta: ni borde, ni fondo de página, ni caja
   rectangular pesada. Una placa luminosa en el color de su formato, que es lo
   que el resto del sitio ya usa para localizar un formato de un vistazo. */
.objeto-cuerpo {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  aspect-ratio: 4 / 5;
  padding: .55rem .6rem;
  border-radius: 12px;
  background:
    linear-gradient(165deg, color-mix(in srgb, var(--color-formato, #3d63c9) 82%, #ffffff) 0%,
                            color-mix(in srgb, var(--color-formato, #3d63c9) 58%, #05070b) 100%);
  box-shadow:
    0 18px 30px -14px rgba(0, 0, 0, .75),
    inset 0 1px 0 rgba(255, 255, 255, .28);
  overflow: hidden;
}
/* Brillo especular: dos máquinas del mismo formato salen iguales de color, y
   este reflejo les da volumen para que se lean como objetos y no como fichas. */
.objeto-cuerpo::after {
  content: "";
  position: absolute;
  inset: 0 45% 55% 0;
  background: linear-gradient(140deg, rgba(255,255,255,.3), transparent 70%);
  pointer-events: none;
}

.objeto-formato {
  font-size: .56rem;
  font-weight: 700;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, .78);
}
.objeto-marca {
  font-size: .9rem;
  font-weight: 800;
  line-height: 1.05;
  color: #fff;
  text-shadow: 0 1px 2px rgba(0, 0, 0, .35);
}
.objeto-cifra {
  display: flex;
  align-items: baseline;
  gap: .18em;
  color: #fff;
}
.objeto-cifra b { font-size: 1.02rem; font-weight: 800; }
.objeto-cifra i { font-size: .58rem; font-style: normal; opacity: .8; }

/* La foto real, cuando exista una VERIFIED. El objeto está preparado para
   ella desde el primer día: hoy el registro de imágenes está vacío porque no
   hay fuente autorizada, y el azulejo de datos ocupa su sitio sin fingir. */
.objeto-foto {
  width: 100%;
  aspect-ratio: 1;
  object-fit: contain;
  background: #fff;
  border-radius: 10px;
}

/* La etiqueta flotante: fuera del cuerpo, como un rótulo en el espacio. */
.objeto-etiqueta {
  display: block;
  margin-top: .4rem;
  font-size: .68rem;
  line-height: 1.25;
  color: rgba(255, 255, 255, .92);
  text-shadow: 0 1px 3px rgba(0, 0, 0, .8);
}
.objeto-etiqueta b { display: block; font-weight: 700; }
.objeto-etiqueta span { color: rgba(255, 255, 255, .62); }

/* Niveles de detalle. El JS pone la clase según la Z: lo que está al fondo no
   necesita rótulo, y quitarlo evita 40 textos ilegibles superpuestos. */
.objeto.lejos  { filter: brightness(.72) saturate(.85); }
.objeto.lejos .objeto-etiqueta { opacity: 0; }
.objeto.medio  { filter: brightness(.9); }
.objeto.cerca .objeto-cuerpo { box-shadow: 0 26px 42px -16px rgba(0,0,0,.8), inset 0 1px 0 rgba(255,255,255,.34); }

/* Pasar por encima o llegar con el tabulador trae el objeto AL FRENTE en 3D,
   no sólo lo agranda. Es lo que garantiza que todo producto sea alcanzable en
   una escena con profundidad, donde lo cercano tapa lo lejano a propósito:
   dentro de un contexto `preserve-3d` el orden de pintado lo decide la Z, así
   que un `z-index` alto no bastaba para sacar de detrás al que está al fondo.
   Con esto, apuntar a un producto medio tapado lo saca al primer plano y ya se
   puede leer y pulsar; y quien navega con teclado recorre los dieciséis. */
.objeto:hover, .objeto:focus-visible {
  --esc: calc(var(--esc-base, 1) * 1.14);
  --pzx: 300;
  outline: none;
  z-index: 60;
}
.objeto:focus-visible .objeto-cuerpo { box-shadow: 0 0 0 3px #fff, 0 0 0 6px var(--accion); }

/* Descartado por los filtros: se aleja, se apaga y deja de estorbar, pero no
   desaparece de golpe. Ver cómo se va es la información. */
.objeto.descartado {
  --op: .16;
  filter: grayscale(1) brightness(.55);
  pointer-events: none;
}
.objeto.descartado .objeto-etiqueta { opacity: 0; }

/* Con un producto enfocado, el resto del universo cede protagonismo. */
.escena.hay-foco .objeto:not(.enfocado) { --op: .28; filter: brightness(.6) saturate(.7); }
.escena.hay-foco .objeto:not(.enfocado) .objeto-etiqueta { opacity: 0; }
.objeto.enfocado { z-index: 80; }
.objeto.seleccionado .objeto-cuerpo { box-shadow: 0 0 0 3px var(--amazon), 0 22px 34px -14px rgba(0,0,0,.8); }

/* Durante la calibración inicial no hay animación: hay que poder leer los
   rectángulos reales sin que estén en vuelo. Dura un fotograma. */
.universo-app.sin-animacion .objeto,
.universo-app.sin-animacion .camara { transition: none; }

/* --- Ejes --------------------------------------------------------------- */

.eje-rotulo {
  position: absolute;
  font-size: .68rem;
  font-weight: 700;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, .5);
  pointer-events: none;
  z-index: 90;
}
.eje-x { bottom: .7rem; left: 50%; transform: translateX(-50%); }
.eje-y { top: 50%; left: .7rem; transform: rotate(-90deg) translateX(-50%); transform-origin: left center; }
.eje-z { top: .7rem; right: .8rem; }
.eje-min, .eje-max {
  position: absolute; bottom: .7rem;
  font-size: .62rem; color: rgba(255,255,255,.38); pointer-events: none; z-index: 90;
}
.eje-min { left: .8rem; }
.eje-max { right: .8rem; }

/* --- Controles ---------------------------------------------------------- */

.universo-barra {
  display: flex;
  flex-wrap: wrap;
  gap: .5rem .9rem;
  align-items: flex-end;
  margin-bottom: .7rem;
}
.universo-eje { display: flex; flex-direction: column; gap: .2rem; }
.universo-eje label {
  font-size: .68rem; font-weight: 700; text-transform: uppercase;
  letter-spacing: .05em; color: var(--texto-tenue);
}
.universo-eje select {
  font: inherit; font-size: .84rem;
  padding: .32rem .5rem;
  border: 1px solid var(--borde-fuerte);
  border-radius: var(--radio-s);
  background: var(--fondo);
  color: var(--texto);
  max-width: 13rem;
}
.universo-estado {
  margin-left: auto;
  font-size: .82rem;
  color: var(--texto-medio);
}
.universo-estado b { color: var(--texto); }

/* --- Necesidades (los filtros como decisiones humanas) ------------------ */

.necesidades { display: flex; flex-wrap: wrap; gap: .4rem; margin-bottom: .7rem; }
.necesidad {
  font: inherit; font-size: .82rem;
  padding: .34rem .7rem;
  border: 1px solid var(--borde-fuerte);
  border-radius: 999px;
  background: var(--fondo);
  color: var(--texto);
  cursor: pointer;
  transition: background .15s, border-color .15s, color .15s;
}
.necesidad:hover { border-color: var(--accion-borde); background: var(--accion-suave); }
.necesidad[aria-pressed="true"] {
  background: var(--accion); border-color: var(--accion-oscuro); color: #fff;
}
.necesidad-limpiar { margin-left: auto; }

/* --- Ficha contextual (product focus) ---------------------------------- */

.foco {
  position: absolute;
  right: 0; top: 0; bottom: 0;
  width: min(340px, 86%);
  padding: 1rem 1.05rem;
  background: rgba(255, 255, 255, .97);
  border-left: 1px solid var(--borde);
  overflow-y: auto;
  z-index: 100;
  /* Cerrada se apaga en el sitio, no se va fuera del encuadre. Con
     `translateX(101%)` el panel quedaba con su rectángulo fuera de la pantalla
     y el verificador del sitio lo cantaba como desbordamiento horizontal, que
     es exactamente la comprobación que hay que respetar y no silenciar. */
  opacity: 0;
  visibility: hidden;
  transform: translateX(14px);
  transition: opacity .3s ease, transform .4s var(--salida), visibility .4s;
}
.foco.abierto { opacity: 1; visibility: visible; transform: none; }
.foco h3 { margin: .1rem 0 .1rem; font-size: 1.05rem; }
.foco .foco-marca { font-size: .72rem; font-weight: 700; text-transform: uppercase; letter-spacing: .06em; color: var(--texto-tenue); }
.foco-encaje {
  display: inline-flex; align-items: baseline; gap: .3em;
  margin: .5rem 0;
  padding: .2rem .55rem;
  border-radius: 999px;
  background: var(--accion-suave);
  border: 1px solid var(--accion-borde);
  font-size: .8rem; font-weight: 700; color: var(--accion-oscuro);
}
.foco-encaje.nulo { background: var(--fondo-3); border-color: var(--borde); color: var(--texto-medio); }
.foco-lista { margin: .5rem 0; padding-left: 1.05rem; font-size: .84rem; line-height: 1.45; }
.foco-lista li { margin-bottom: .2rem; }
.foco-cerrar {
  position: absolute; top: .5rem; right: .5rem;
  width: 30px; height: 30px; line-height: 1;
  border: 1px solid var(--borde); border-radius: 50%;
  background: var(--fondo); color: var(--texto-medio);
  cursor: pointer; font-size: 1.1rem;
}
.foco-acciones { display: flex; flex-direction: column; gap: .45rem; margin-top: .7rem; }

/* --- Bandeja de comparación -------------------------------------------- */

.bandeja {
  position: absolute;
  left: 50%; bottom: .8rem;
  transform: translate(-50%, 130%);
  display: flex; align-items: center; gap: .5rem;
  padding: .45rem .6rem;
  border-radius: 999px;
  background: rgba(255, 255, 255, .97);
  box-shadow: var(--sombra-alta);
  z-index: 95;
  transition: transform .35s var(--salida);
}
.bandeja.visible { transform: translate(-50%, 0); }
.bandeja-hueco {
  width: 46px; height: 46px;
  display: grid; place-items: center;
  border-radius: 8px;
  background: var(--fondo-3);
  border: 1px dashed var(--borde-fuerte);
  font-size: .58rem; font-weight: 700; text-align: center;
  color: var(--texto-tenue);
  position: relative;
}
.bandeja-hueco.lleno { border-style: solid; background: var(--fondo); }
.bandeja-hueco .quitar {
  position: absolute; top: -6px; right: -6px;
  width: 18px; height: 18px; line-height: 16px;
  border-radius: 50%; border: 1px solid var(--borde-fuerte);
  background: var(--fondo); cursor: pointer; font-size: .7rem;
}

/* --- Modo VS ------------------------------------------------------------ */

.escena.modo-vs .objeto:not(.seleccionado) { --op: .07; filter: grayscale(1) brightness(.4); }
.vs-rotulo {
  position: absolute; top: 12%; left: 50%;
  transform: translateX(-50%) scale(.7);
  opacity: 0;
  font-size: clamp(1.5rem, 5vw, 2.6rem); font-weight: 800; letter-spacing: .1em;
  color: #fff; text-shadow: 0 4px 18px rgba(0,0,0,.8);
  z-index: 92; pointer-events: none;
  transition: transform .5s var(--salida), opacity .4s;
}
.escena.modo-vs .vs-rotulo { opacity: 1; transform: translateX(-50%) scale(1); }
.vs-panel {
  position: absolute; left: 50%; bottom: 4.6rem;
  transform: translateX(-50%);
  width: min(620px, 92%);
  padding: .8rem .95rem;
  border-radius: var(--radio);
  background: rgba(255,255,255,.97);
  box-shadow: var(--sombra-alta);
  z-index: 96;
  font-size: .84rem;
  display: none;
}
.escena.modo-vs .vs-panel { display: block; }
.vs-panel ul { margin: .35rem 0 .6rem; padding-left: 1.05rem; line-height: 1.45; }

/* --- Modo lista (fallback y preferencia) -------------------------------- */

.universo-app.modo-lista .escena { display: none; }
.universo-lista { display: none; }
.universo-app.modo-lista .universo-lista { display: block; }

/* --- Sin JavaScript ----------------------------------------------------- */
/* La escena sin JS es una lista legible: los objetos se desapilan y recuperan
   el flujo del documento. Nunca una pantalla vacía. */
.sin-js .escena {
  height: auto; perspective: none; overflow: visible;
  background: var(--fondo-2); padding: .8rem;
  display: flex; flex-wrap: wrap; gap: .6rem;
}
.sin-js .escena::before, .sin-js .escena::after, .sin-js .eje-rotulo,
.sin-js .eje-min, .sin-js .eje-max { display: none; }
.sin-js .camara { position: static; display: contents; }
.sin-js .objeto { position: static; transform: none; opacity: 1; filter: none; }
.sin-js .objeto-etiqueta { color: var(--texto); text-shadow: none; }
.sin-js .objeto-etiqueta span { color: var(--texto-medio); }

/* --- Piezas propias de esta capa ---------------------------------------- */
/* Viven aquí y no en el CSS global porque sólo las usa el universo: quien
   entra a comparar no tiene por qué descargar sus reglas. */

.btn-peq { padding: .34rem .6rem; font-size: .78rem; }

.lista-simple { list-style: none; padding: 0; margin: .6rem 0 0; }
.lista-simple li {
  display: flex; justify-content: space-between; gap: 1rem;
  padding: .45rem .1rem; border-bottom: 1px solid var(--borde);
  font-size: .9rem;
}
.lista-simple li span { color: var(--texto-medio); font-size: .8rem; }

/* --- Móvil -------------------------------------------------------------- */

@media (max-width: 720px) {
  .universo-app { --obj: 66px; }
  .escena { height: clamp(400px, 60vh, 500px); perspective: 900px; }

  /* Lo de arriba tiene que caber. Apiladas en vertical, las veinte fichas de
     necesidad dejaban la escena entera por debajo del pliegue: en un móvil de
     844 px de alto el primer producto caía en y=1198, así que la experiencia
     empezaba con una pantalla de controles.
     La primera solución fue una fila deslizable con el dedo, y estaba mal: los
     botones que se salen del carril tienen su rectángulo fuera de la pantalla y
     el verificador lo detecta como desbordamiento horizontal. Este sitio evita
     el scroll lateral a propósito —el comparador oculta columnas en vez de
     desplazarlas—, así que aquí se hace lo mismo que allí: reordenar.
     Las necesidades bajan DEBAJO de la escena. Lo primero que se ve en un móvil
     es el mercado; los filtros están a un pulgar de distancia. */
  .universo-app { display: flex; flex-direction: column; }
  .universo-barra  { order: 1; }
  .escena          { order: 2; }
  .necesidades     { order: 3; margin: .6rem 0 0; }
  .universo-nota   { order: 4; }
  .universo-lista  { order: 5; }
  .necesidad { font-size: .78rem; padding: .3rem .6rem; }

  .universo-barra { gap: .35rem .5rem; margin-bottom: .5rem; }
  .universo-eje { flex: 1 1 6.5rem; }
  .universo-eje label { font-size: .58rem; }
  .universo-eje select { max-width: none; width: 100%; font-size: .78rem; padding: .28rem .4rem; }
  .universo-estado { order: 9; font-size: .78rem; }
  /* El blur y la saturación por capa cuestan caro en GPU móvil y el beneficio
     es cosmético: la profundidad ya la dan escala y posición. */
  .objeto.lejos, .objeto.medio { filter: none; opacity: var(--op, 1); }
  .foco { width: 100%; border-left: 0; }
  .universo-estado { margin-left: 0; width: 100%; }
}

/* --- Menos movimiento --------------------------------------------------- */
/* Quien pide menos movimiento sigue teniendo el universo: lo que se quita es
   la animación, no la información. Los productos aparecen ya colocados. */
@media (prefers-reduced-motion: reduce) {
  .camara, .objeto, .foco, .bandeja, .vs-rotulo { transition: none !important; }
  .escena { cursor: default; }
}
