/* ================================================================
   HUB DINÁMICO — index.html (feed de inicio)
   ----------------------------------------------------------------
   Se apoya en las variables y clases globales de styles.css
   (--pl-grad, --pl-cyan, .btn-primary, .reveal…).

   La idea del layout: una landing apila bandas a todo el ancho; un
   feed tiene una COLUMNA de contenido y un RAIL al lado. Eso es lo
   que separa "página que lees una vez" de "página que frecuentas".

     [hero compacto]      ← identidad, corto
     [estado]             ← avatar + piezas + racha, ancho completo
     [ main | rail ]      ← la corriente + reto, eventos y descubrimiento

   Todas las animaciones usan transform/opacity/filter, y se apagan
   con body.perf-low o prefers-reduced-motion.
   ================================================================ */

.hub-shell { max-width: 1180px; margin: 0 auto; padding: 0 1.25rem; }

/* ================================================================
   Hero compacto
   ================================================================ */
/* El hero de landing ocupa una pantalla entera; aquí el feed tiene
   que asomar casi de inmediato, así que se recorta a la mitad. */
body.pl-hub .hub-hero {
  padding-top: calc(var(--nav-height) + 1.2rem);
  padding-bottom: 0;
  /* ── De aquí venía la línea que cruzaba el ancho ──
     .hero trae overflow:hidden (styles.css) porque en la landing es un
     bloque alto donde los adornos tienen sitio de sobra. Al recortarlo
     para el Hub, dos cosas quedaron cortadas justo en su borde inferior:
     el halo del logo (drop-shadow de 38px, y encima heroFloat lo mueve)
     y .hero-spark-a, que mide 16rem y arrancaba al 18%. Un degradado
     difuminado cortado a ras deja un canto duro: esa era la "línea".
     Sin recorte, el halo se desvanece por debajo con normalidad y
     además queda DETRÁS de la tarjeta de estado, que se pinta después. */
  overflow: visible;
}

/* Los destellos estaban pensados para un hero de pantalla completa;
   en este, más bajo, solo asoman por el borde. */
body.pl-hub .hub-hero .hero-spark { display: none; }

/* El logo mantiene sus anillos y sus mecánicas de hover/clic; solo se
   escala para que el feed no quede empujado por debajo del pliegue.
   Al quitar el subtítulo, el margen inferior del .logo-wrap (1.35rem
   en styles.css) dejaba de sostener nada: se anula para que la tarjeta
   de estado suba en vez de flotar tras un hueco. */
body.pl-hub .hub-hero .logo-wrap {
  width: min(100%, 330px);
  margin-bottom: 0;
  padding: 0.9rem 0.8rem 0;
}

body.pl-hub .hub-hero .hero-logo { width: min(100%, 280px); }

/* ================================================================
   Piezas compartidas
   ================================================================ */
.hub-eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  padding: 0.28rem 0.85rem;
  border-radius: 999px;
  border: 1px solid var(--pl-border);
  background: var(--pl-panel-alt);
  font-size: 0.74rem;
  font-weight: 800;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--pl-soft);
}

.hub-section-head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
  margin-bottom: 1rem;
}

.hub-section-title {
  font-family: var(--font-heading);
  font-size: clamp(1.1rem, 2.2vw, 1.4rem);
  font-weight: 900;
  letter-spacing: 0.02em;
  color: var(--pl-text);
  margin: 0.5rem 0 0;
}

.hub-skeleton {
  border-radius: 20px;
  background: linear-gradient(100deg, rgba(255,255,255,0.03) 30%, rgba(255,255,255,0.07) 50%, rgba(255,255,255,0.03) 70%);
  background-size: 220% 100%;
  animation: hubShimmer 1.5s ease-in-out infinite;
}

@keyframes hubShimmer {
  0%   { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}

/* ================================================================
   BLOQUE A — Cabecera de estado
   ================================================================ */
.hub-status { margin-top: 1.25rem; }
/* Mismo alto que la tarjeta ya montada, para que no dé un salto */
.hub-status-skeleton { height: 165px; }

/* Un elemento con [hidden] Y una regla de display propia pierde el
   hidden: la especificidad de clase empata con la del atributo, y
   gana la declarada después (la nuestra). Mismo patrón que ya usa
   el resto del sitio (.ca-modal[hidden], .plc-overlay[hidden]…). */
.hub-status-card[hidden],
.hub-status-guest[hidden] { display: none; }

.hub-status-card {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 0.9rem;
  padding: 1.1rem 1.25rem 1.2rem;
  border-radius: 22px;
  border: 1px solid rgba(141, 238, 255, 0.22);
  background:
    radial-gradient(120% 150% at 0% 0%, rgba(141, 238, 255, 0.13), transparent 58%),
    radial-gradient(120% 150% at 100% 100%, rgba(229, 89, 177, 0.14), transparent 58%),
    var(--pl-panel);
  box-shadow: 0 26px 60px -34px rgba(169, 106, 231, 0.85);
  overflow: hidden;
}

/* Aquí había un barrido de luz (::after con inset:0 y un degradado que
   cruzaba la tarjeta cada 9s). El panel de la racha es casi transparente,
   así que ese destello se veía pasar POR DETRÁS de él como una línea
   clara que resaltaba sobre el fondo. Quitarlo también ahorra una
   animación infinita que no aportaba nada. */

/* Fila superior: identidad a la izquierda, piezas a la derecha */
.hub-id {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 1rem;
  min-width: 0;
}

.hub-id-row { display: flex; align-items: center; gap: 0.9rem; min-width: 0; }

/* Avatar con marco (frames.css pinta .frame-overlay) */
.hub-avatar {
  position: relative;
  flex-shrink: 0;
  width: 50px;
  height: 50px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  overflow: visible;
  background: var(--pl-grad);
  color: #12001f;
  font-family: var(--font-heading);
  font-weight: 900;
  font-size: 1rem;
  text-decoration: none;
  transition: transform 0.22s ease;
}

.hub-avatar:hover { transform: scale(1.06); }
.hub-avatar:focus-visible { outline: 2px solid var(--pl-cyan); outline-offset: 4px; }

.hub-avatar > img:not(.frame-overlay) {
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: 50%;
  display: block;
}

.hub-id-copy { min-width: 0; }
.hub-id-hi { margin: 0; font-size: 0.78rem; font-weight: 700; color: var(--pl-muted); }

.hub-id-name {
  margin: 0.1rem 0 0;
  font-family: var(--font-heading);
  font-size: 1.15rem;
  font-weight: 900;
  color: var(--pl-text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Saldo de piezas */
.hub-pieces {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  flex-shrink: 0;
  padding: 0.55rem 1rem;
  border-radius: 999px;
  border: 1px solid rgba(255, 214, 102, 0.32);
  background: rgba(255, 200, 80, 0.1);
  margin: 0;
}

.hub-pieces .pl-pieces-ico { font-size: 1.1rem; }

.hub-pieces .pl-pieces-num {
  font-family: var(--font-heading);
  font-size: 1.1rem;
  font-weight: 900;
  color: #ffd97a;
  font-variant-numeric: tabular-nums;
}

.hub-pieces .pl-pieces-label { font-size: 0.8rem; color: rgba(255, 255, 255, 0.62); }

.hub-id-acciones { display: flex; align-items: center; gap: 0.6rem; flex-wrap: wrap; }

/* Atajo al taller. Antes vivía en la lista "Ir a…" del rail; al quitarla
   se quedó sin sitio, y es el único enlace del Hub que la barra de
   navegación NO tiene. Aquí ocupa el hueco muerto que quedaba junto al
   saldo en vez de gastar una tarjeta entera del rail. */
.hub-crear-btn {
  display: none;
  align-items: center;
  gap: 0.4rem;
  flex-shrink: 0;
  padding: 0.55rem 1rem;
  border-radius: 999px;
  border: 1px solid rgba(169, 106, 231, 0.45);
  background: rgba(169, 106, 231, 0.14);
  color: var(--pl-text);
  font-weight: 800;
  font-size: 0.85rem;
  text-decoration: none;
  transition: background 0.2s ease, transform 0.2s ease, border-color 0.2s ease;
}

body.pl-puede-crear .hub-crear-btn { display: inline-flex; }

.hub-crear-btn:hover {
  background: rgba(169, 106, 231, 0.26);
  border-color: rgba(169, 106, 231, 0.8);
  transform: translateY(-2px);
}

.hub-crear-btn:focus-visible { outline: 2px solid var(--pl-cyan); outline-offset: 3px; }

/* ── La racha, condensada ────────────────────────────────────────
   Es el módulo del perfil (.pv-checkin y sus hijos .ci-*), pero allí
   se apila en 4 filas altas porque vive en una columna estrecha.
   Aquí hay ancho de sobra y falta alto, así que se re-maqueta en dos
   filas de tres columnas:

     [RACHA DIARIA]  [ · · · los 7 días · · · ]
     [ barra del marco ............ ] [ botón ]

   El rótulo y la racha ocupan una columna estrecha a la izquierda y
   los días se colocan A SU LADO, en vez de debajo: así desaparece la
   franja vacía que quedaba a la derecha del título. */
.hub-checkin { position: relative; z-index: 1; }

.hub-checkin.pv-checkin {
  display: grid;
  grid-template-columns: minmax(7rem, 10rem) minmax(0, 1fr) minmax(10rem, 14rem);
  grid-template-areas:
    "head  pista pista"
    "marco marco accion";
  gap: 0.6rem 1rem;
  align-items: center;
  padding: 0.85rem 1rem 0.9rem;
  /* Sin borde: el panel ya se distingue por su fondo, y ese filo ámbar
     era justo la línea que cruzaba de lado a lado. Se deja el 1px en
     transparente para no mover nada de sitio. */
  border-color: transparent;
  /* Fondo propio y opaco: con el 3% de blanco que traía, los degradados
     de la tarjeta se transparentaban a través del panel y dejaban
     costuras visibles justo donde cambia el color de fondo.
     --pl-panel apilado dos veces en vez de un color fijo: sumando sus
     alfas queda opaco, y sigue el tema activo (en el claro --pl-panel
     es casi blanco, así que un color oscuro fijo lo habría roto). */
  background:
    radial-gradient(120% 140% at 100% 0%, rgba(255, 168, 61, 0.1), transparent 62%),
    linear-gradient(var(--pl-panel), var(--pl-panel)),
    var(--pl-panel);
}

/* Las celdas de un grid no bajan de su min-content por defecto
   (min-width:auto). Si el texto del marco o el botón no caben, el panel
   se ensancha MÁS que la tarjeta, y como la tarjeta recorta
   (overflow:hidden) se pierden sus bordes laterales y solo quedan a la
   vista las líneas de arriba y abajo, cruzando de lado a lado. */
.hub-checkin .ci-marco,
.hub-checkin .ci-row,
.hub-checkin .ci-pista { min-width: 0; }

.hub-checkin .ci-head  { grid-area: head; }
.hub-checkin .ci-pista { grid-area: pista; }
.hub-checkin .ci-marco { grid-area: marco; }
.hub-checkin .ci-row   { grid-area: accion; }
/* El acuse de "+10 piezas" aparece al reclamar: a lo ancho, al final */
.hub-checkin .ci-flash { grid-column: 1 / -1; }

/* Casillas anchas y bajas: el cuadrado de 4.2rem del perfil es lo que
   disparaba el alto. Sin aspect-ratio se estiran a lo ancho solas. */
.hub-checkin .ci-dia-caja {
  max-width: none;
  aspect-ratio: auto;
  height: 2.4rem;
  border-radius: 12px;
}

.hub-checkin .ci-pista { gap: 0.4rem; }
.hub-checkin .ci-dia { gap: 0.14rem; }
.hub-checkin .ci-dia-ico { font-size: 0.95rem; }
.hub-checkin .ci-dia-premio { font-size: 0.68rem; }
.hub-checkin .ci-dia-num { font-size: 0.55rem; }
/* En su columna estrecha, el rótulo y la píldora de racha se apilan en
   vez de separarse a los extremos (.ci-head es flex con
   space-between en checkin.css). */
.hub-checkin .ci-head {
  flex-direction: column;
  align-items: flex-start;
  justify-content: center;
  gap: 0.4rem;
}

.hub-checkin .ci-title { font-size: 0.68rem; line-height: 1.25; }
.hub-checkin .ci-streak { padding: 0.2rem 0.6rem; }
.hub-checkin .ci-streak-num { font-size: 0.95rem; }
.hub-checkin .ci-marco { padding: 0.5rem 0.7rem 0.55rem; gap: 0.25rem; }
.hub-checkin .ci-marco-pie { font-size: 0.68rem; }
.hub-checkin .ci-marco-barra { height: 6px; }

/* Botón y nota, apilados en su celda para igualar el alto del marco */
.hub-checkin .ci-row { flex-direction: column; align-items: stretch; gap: 0.3rem; }
.hub-checkin .ci-btn { width: 100%; justify-content: center; padding-block: 0.5rem; font-size: 0.88rem; }
.hub-checkin .ci-note { flex: 0 1 auto; font-size: 0.7rem; text-align: center; }

/* Sin sitio para dos celdas: vuelve a apilarse */
/* Sin ancho para tres columnas, todo se apila y el rótulo vuelve a
   extenderse a lo ancho con la racha en el otro extremo. */
@media (max-width: 780px) {
  .hub-checkin.pv-checkin {
    grid-template-columns: 1fr;
    grid-template-areas: "head" "pista" "marco" "accion";
  }

  .hub-checkin .ci-head {
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
  }
}

/* Invitado */
.hub-status-guest {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.2rem;
  flex-wrap: wrap;
  padding: 1.4rem 1.6rem;
  border-radius: 24px;
  border: 1px solid var(--pl-border);
  background:
    radial-gradient(110% 150% at 0% 0%, rgba(141, 238, 255, 0.1), transparent 60%),
    var(--pl-panel);
}

.hub-status-guest-title {
  margin: 0 0 0.3rem;
  font-family: var(--font-heading);
  font-size: 1.1rem;
  font-weight: 900;
  color: var(--pl-text);
}

.hub-status-guest-text { margin: 0; font-size: 0.9rem; color: var(--pl-muted); max-width: 48ch; }

/* ================================================================
   LAYOUT DEL FEED — columna + rail
   ================================================================ */
.hub-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 320px;
  gap: 1.75rem;
  align-items: start;
  margin-top: 2.5rem;
  padding-bottom: 4rem;
}

.hub-main { min-width: 0; display: flex; flex-direction: column; gap: 2.5rem; }

/* El rail se queda a la vista y se desplaza POR SU CUENTA.
   ─ sticky + align-self:start lo fijan junto a la barra.
   ─ max-height + overflow-y le dan su propia barra cuando su
     contenido es más alto que la ventana (si no, sus últimas
     tarjetas quedarían fuera de alcance para siempre).
   ─ overscroll-behavior:contain corta el encadenado: al llegar al
     final del rail el scroll NO salta a la página. Es lo que hace
     que cada columna se sienta independiente.
   El padding evita que overflow recorte el brillo de las tarjetas. */
.hub-rail {
  position: sticky;
  top: calc(var(--nav-height) + 1rem);
  align-self: start;
  max-height: calc(100vh - var(--nav-height) - 2rem);
  overflow-y: auto;
  overscroll-behavior: contain;
  display: flex;
  flex-direction: column;
  gap: 1.1rem;
  min-width: 0;
  padding: 0.35rem 0.5rem 0.35rem 0.35rem;
  scrollbar-width: thin;
  scrollbar-color: rgba(255, 255, 255, 0.2) transparent;
}

/* CLAVE: en un contenedor flex con altura acotada, los hijos se ENCOGEN
   por defecto (flex-shrink:1) antes de provocar scroll. Por eso el Reto
   Exprés salía aplastado y con el título cortado en vez de hacer que el
   rail se desplazara. Con flex-shrink:0 cada tarjeta conserva su alto y
   el desbordamiento pasa al scroll del rail, que es lo que queremos. */
.hub-rail > * { flex-shrink: 0; }

/* Cuando queda contenido por debajo, el borde inferior se desvanece en
   vez de cortar una tarjeta por la mitad. La clase la pone hub.js
   (vigilarRail), porque el CSS no sabe si hay desbordamiento. */
.hub-rail.has-overflow {
  -webkit-mask-image: linear-gradient(180deg, #000 calc(100% - 3rem), transparent 100%);
  mask-image: linear-gradient(180deg, #000 calc(100% - 3rem), transparent 100%);
}

.hub-rail::-webkit-scrollbar { width: 8px; }
.hub-rail::-webkit-scrollbar-track { background: transparent; }
.hub-rail::-webkit-scrollbar-thumb {
  background: rgba(255, 255, 255, 0.16);
  border-radius: 999px;
  border: 2px solid transparent;
  background-clip: content-box;
}
.hub-rail::-webkit-scrollbar-thumb:hover { background: rgba(141, 238, 255, 0.4); background-clip: content-box; }

@media (max-width: 1024px) {
  .hub-layout { grid-template-columns: minmax(0, 1fr); gap: 2.5rem; }

  /* En una sola columna el rail se suelta y sube POR ENCIMA del muro:
     debajo de un scroll infinito nunca se llegaría a él. */
  .hub-rail {
    position: static;
    order: -1;
    max-height: none;
    overflow: visible;
    padding: 0;
  }
}

/* ================================================================
   Tarjetas del rail
   ================================================================ */
.hub-card {
  padding: 1.15rem 1.2rem 1.25rem;
  border-radius: 20px;
  border: 1px solid var(--pl-border);
  background: var(--pl-panel);
}

.hub-card-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.6rem;
  margin-bottom: 0.9rem;
}

.hub-card-title {
  margin: 0;
  font-family: var(--font-heading);
  font-size: 0.92rem;
  font-weight: 900;
  letter-spacing: 0.02em;
  color: var(--pl-text);
}

.hub-card-link {
  flex-shrink: 0;
  font-size: 0.78rem;
  font-weight: 800;
  color: var(--pl-cyan);
  text-decoration: none;
}

.hub-card-link:hover { text-decoration: underline; }

/* ── Eventos vivos ─────────────────────────────────────────────── */
.hub-eventos { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.4rem; }

.hub-evento {
  display: grid;
  gap: 0.15rem;
  padding: 0.6rem 0.7rem;
  border-radius: 14px;
  text-decoration: none;
  color: inherit;
  transition: background 0.2s ease, transform 0.2s ease;
}

.hub-evento:hover { background: rgba(229, 89, 177, 0.08); transform: translateX(3px); }
.hub-evento:focus-visible { outline: 2px solid var(--pl-cyan); outline-offset: 2px; }

.hub-evento-estado {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  font-size: 0.7rem;
  font-weight: 800;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  color: var(--pl-muted);
}

.hub-evento-estado.is-vivo { color: #ff8fa8; }

.hub-evento-nombre {
  font-size: 0.9rem;
  font-weight: 700;
  color: var(--pl-text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.hub-evento-fecha { font-size: 0.76rem; color: var(--pl-muted); }

/* ── Filas compactas de actividad ────────────────────────────────
   Aquí vivían una nube de etiquetas ("Explora") y una lista de atajos
   ("Ir a…"). La nube repetía los filtros de la Galería y los atajos
   repetían la barra de navegación de arriba: dos tarjetas de rail
   gastadas en decir lo que la página ya decía. En su lugar van dos
   listas que no están en ningún otro sitio de la portada — lo que
   guardaste para después y lo más jugado — y ambas comparten esta
   misma fila: carátula pequeña, título y una línea de apoyo. */
.hub-mini-list,
.hub-pop-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}

.hub-mini {
  display: flex;
  align-items: center;
  gap: 0.65rem;
  padding: 0.4rem 0.5rem;
  border-radius: 14px;
  color: var(--pl-text);
  text-decoration: none;
  min-width: 0;
  transition: background 0.2s ease, transform 0.2s ease;
}

.hub-mini:hover { background: rgba(var(--tipo), 0.1); transform: translateX(3px); }
.hub-mini:focus-visible { outline: 2px solid rgb(var(--tipo)); outline-offset: 2px; }

/* Puesto en la lista de lo más jugado */
.hub-mini-num {
  flex-shrink: 0;
  width: 1.1rem;
  text-align: center;
  font-family: var(--font-heading);
  font-size: 0.95rem;
  font-weight: 900;
  color: rgba(var(--tipo), 0.85);
  font-variant-numeric: tabular-nums;
}

.hub-mini-cover {
  position: relative;
  flex-shrink: 0;
  width: 46px;
  height: 46px;
  border-radius: 10px;
  overflow: hidden;
  border: 1px solid rgba(var(--tipo), 0.3);
  background: rgba(var(--tipo), 0.1);
  display: grid;
  place-items: center;
}

.hub-mini-img { width: 100%; height: 100%; object-fit: cover; display: block; }

.hub-mini-img-vacia {
  display: grid;
  place-items: center;
  font-size: 1.3rem;
  opacity: 0.8;
}

.hub-mini-copy { display: flex; flex-direction: column; gap: 0.1rem; min-width: 0; }

.hub-mini-titulo {
  font-weight: 800;
  font-size: 0.88rem;
  line-height: 1.25;
  /* Dos líneas como mucho: en 320px un título largo desmontaría la fila */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.hub-mini-sub {
  font-size: 0.74rem;
  font-weight: 600;
  color: var(--pl-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ================================================================
   BLOQUE C — Reto Exprés
   ================================================================ */
/* Vive en el rail (≈320px), así que va en vertical y compacto: el
   título arriba con las fichas, y debajo la actividad del día. */
.hub-challenge-card {
  position: relative;
  display: flex;
  flex-direction: column;
  padding: 1.15rem 1.2rem 1.25rem;
  border-radius: 20px;
  border: 1px solid rgba(229, 89, 177, 0.85);
  background-color: #150a20;
  /* La portada de la actividad la inyecta hub.js como background-image;
     estos degradados quedan por encima cuando NO hay portada. */
  background-image:
    radial-gradient(140% 170% at 0% 0%, rgba(141, 238, 255, 0.13), transparent 58%),
    radial-gradient(140% 170% at 100% 100%, rgba(229, 89, 177, 0.16), transparent 58%);
  background-size: cover;
  background-position: center;
  box-shadow:
    0 0 20px -3px rgba(229, 89, 177, 0.6),
    inset 0 0 16px -8px rgba(229, 89, 177, 0.7),
    0 24px 54px -32px rgba(229, 89, 177, 0.9);
  overflow: hidden;
}

/* Con portada: velo oscuro para que el texto siga siendo legible sea
   cual sea la imagen, y un poco de desenfoque para que no compita. */
.hub-challenge-card.has-cover::before {
  content: "";
  position: absolute;
  inset: 0;
  background:
    linear-gradient(115deg, rgba(9, 4, 15, 0.95) 0%, rgba(9, 4, 15, 0.82) 45%, rgba(9, 4, 15, 0.55) 100%),
    linear-gradient(0deg, rgba(9, 4, 15, 0.7), rgba(9, 4, 15, 0.1));
  -webkit-backdrop-filter: blur(2px);
  backdrop-filter: blur(2px);
}

/* ── Borde neón ──────────────────────────────────────────────────
   Dos capas: el borde rosa fijo de la tarjeta y, encima, un filo
   cian que entra y sale. Al alternar los dos colores da el latido de
   letrero de neón sin animar el box-shadow (que repinta): solo se
   mueve la opacidad de un pseudo-elemento, que va en la GPU. */
.hub-challenge-card::after {
  content: "";
  position: absolute;
  inset: -1px;
  border-radius: inherit;
  border: 1px solid rgba(141, 238, 255, 0.85);
  box-shadow: 0 0 18px -2px rgba(141, 238, 255, 0.55), inset 0 0 14px -6px rgba(141, 238, 255, 0.6);
  opacity: 0;
  pointer-events: none;
  z-index: 2;
  animation: hubNeonPulso 3.4s ease-in-out infinite;
}

@keyframes hubNeonPulso {
  0%, 100% { opacity: 0; }
  50%      { opacity: 1; }
}

/* El contenido tiene que quedar por encima del velo */
.hub-challenge-head,
.hub-challenge-content { position: relative; z-index: 1; }

.hub-challenge-content { flex: 1; }

.hub-challenge-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.6rem;
  margin-bottom: 0.9rem;
}

.hub-challenge-kicker {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  margin: 0;
  font-family: var(--font-heading);
  font-weight: 900;
  font-size: 0.92rem;
  letter-spacing: 0.02em;
  background: var(--pl-grad);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.hub-challenge-rerolls { display: flex; align-items: center; gap: 0.5rem; flex-shrink: 0; }
.hub-reroll-pips { display: inline-flex; gap: 0.28rem; }

/* Fichas de reroll: se apagan conforme se gastan */
.hub-reroll-pip {
  width: 0.55rem;
  height: 0.55rem;
  border-radius: 50%;
  background: var(--pl-cyan);
  box-shadow: 0 0 10px -2px var(--pl-cyan);
  transition: background 0.3s ease, box-shadow 0.3s ease, transform 0.3s ease;
}

.hub-reroll-pip.is-gastado { background: rgba(255, 255, 255, 0.16); box-shadow: none; transform: scale(0.8); }

/* Solo icono: en el rail no cabe un botón con etiqueta */
.hub-reroll-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.1rem;
  height: 2.1rem;
  padding: 0;
  border-radius: 999px;
  border: 1px solid var(--pl-border);
  background: rgba(255, 255, 255, 0.05);
  color: var(--pl-text);
  font: inherit;
  font-size: 0.85rem;
  font-weight: 700;
  cursor: pointer;
  transition: transform 0.2s ease, border-color 0.2s ease, background 0.2s ease;
}

.hub-reroll-btn:hover:not(:disabled) {
  border-color: var(--pl-cyan);
  background: rgba(141, 238, 255, 0.1);
  transform: translateY(-1px);
}

.hub-reroll-btn:focus-visible { outline: 2px solid var(--pl-cyan); outline-offset: 3px; }
.hub-reroll-btn:disabled { opacity: 0.42; cursor: not-allowed; }
.hub-reroll-ico { display: inline-block; font-size: 1rem; }

.hub-challenge-content { min-height: 8.5rem; }

.hub-challenge-type {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  font-size: 0.76rem;
  font-weight: 800;
  color: var(--pl-cyan);
  margin-bottom: 0.3rem;
}

.hub-challenge-title {
  font-family: var(--font-heading);
  font-size: 1.05rem;
  line-height: 1.3;
  margin: 0 0 0.4rem;
  color: var(--pl-text);
}

/* En columna estrecha los datos se apilan en vez de ir en una línea
   que se rompería a la mitad. */
.hub-challenge-meta {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  color: var(--pl-muted);
  font-size: 0.8rem;
  margin: 0 0 0.9rem;
}

.hub-challenge-cta { display: flex; justify-content: center; width: 100%; }
.hub-challenge-done,
.hub-challenge-empty,
.hub-challenge-guest { color: var(--pl-soft); margin: 0 0 1rem; line-height: 1.55; }
.hub-challenge-note { margin: 0.9rem 0 0; font-size: 0.8rem; color: rgba(255, 255, 255, 0.45); }

/* Lo que paga el reto: mismo dorado que el saldo de piezas */
.hub-challenge-premio {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  font-weight: 800;
  color: #ffd97a;
}

p.hub-challenge-premio { margin: 0 0 0.9rem; }

/* Ruleta gacha: el contenido sale por arriba difuminado y el nuevo
   entra por abajo frenando. El cambio de datos ocurre en el punto
   ciego (ver hub.js). */
.hub-challenge-content.gacha-spin-active {
  animation: hubSlotMachine 0.6s cubic-bezier(0.25, 1, 0.5, 1);
}

@keyframes hubSlotMachine {
  0%   { transform: translateY(0);     filter: blur(0);   opacity: 1; }
  30%  { transform: translateY(-26px); filter: blur(7px); opacity: 0; }
  31%  { transform: translateY(26px);  filter: blur(7px); opacity: 0; }
  100% { transform: translateY(0);     filter: blur(0);   opacity: 1; }
}

.hub-reroll-ico.icon-spin { animation: hubSpinRound 0.6s cubic-bezier(0.3, 1.4, 0.5, 1); }

@keyframes hubSpinRound {
  from { transform: rotate(0deg); }
  to   { transform: rotate(360deg); }
}

/* ================================================================
   BLOQUE D — Muro de novedades
   ================================================================ */
/* Los filtros se quedan pegados bajo la barra mientras haces scroll,
   como la barra de pestañas de una app. */
.hub-feed-filters {
  position: sticky;
  top: calc(var(--nav-height) + 0.5rem);
  z-index: 5;
  display: flex;
  gap: 0.5rem;
  flex-wrap: wrap;
  margin-bottom: 1.25rem;
  padding: 0.55rem;
  border-radius: 999px;
  border: 1px solid var(--pl-border);
  background: rgba(12, 8, 20, 0.82);
  -webkit-backdrop-filter: blur(14px);
  backdrop-filter: blur(14px);
}

.hub-chip {
  padding: 0.42rem 0.95rem;
  border-radius: 999px;
  border: 1px solid transparent;
  background: transparent;
  color: var(--pl-muted);
  font: inherit;
  font-size: 0.84rem;
  font-weight: 700;
  cursor: pointer;
  transition: color 0.2s ease, background 0.2s ease;
}

.hub-chip:hover { color: var(--pl-text); background: rgba(255, 255, 255, 0.05); }
.hub-chip:focus-visible { outline: 2px solid var(--pl-cyan); outline-offset: 2px; }

.hub-chip.is-activo {
  color: #16031f;
  background: var(--pl-grad);
  box-shadow: 0 8px 20px -10px rgba(169, 106, 231, 0.9);
}

/* Una sola columna de posts, como cualquier feed: la rejilla de
   mosaicos se parecía demasiado a la galería. */
.hub-feed-list { display: flex; flex-direction: column; gap: 1.1rem; }

/* --tipo llega desde el JS en canales RGB: cada tipo de puzzle (y cada
   categoría de aviso) tiñe su propia tarjeta. Un solo valor gobierna
   borde, insignia, fichas y brillo. */
.hub-feed-card {
  --tipo: 141, 238, 255;
  position: relative;
  /* El navegador se salta el pintado de las tarjetas que no se ven y da
     por hecho el alto de reserva. 340px se quedaba MUY corto para una
     tarjeta de juego (portada 16:9 + cabecera + pie), y esa diferencia
     se nota en el pulgar de la barra de scroll, que encoge y crece al
     bajar. La segunda declaración (`auto`) hace que además recuerde el
     alto real una vez pintada; los navegadores que no la entienden se
     quedan con la de arriba. */
  content-visibility: auto;
  contain-intrinsic-size: 0 620px;
  contain-intrinsic-size: auto 620px;
  display: flex;
  flex-direction: column;
  border-radius: 20px;
  overflow: hidden;
  border: 1px solid var(--pl-border);
  background:
    radial-gradient(120% 90% at 50% 0%, rgba(var(--tipo), 0.08), transparent 70%),
    var(--pl-panel);
  transition: border-color 0.22s ease, box-shadow 0.22s ease, transform 0.22s ease;
}

.hub-feed-card:hover {
  transform: translateY(-3px);
  border-color: rgba(var(--tipo), 0.5);
  box-shadow: 0 20px 44px -26px rgba(var(--tipo), 0.65);
}

/* Filo de color en el canto izquierdo: identifica el tipo de un
   vistazo al recorrer el muro. */
.hub-feed-card::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 3px;
  background: linear-gradient(180deg, rgb(var(--tipo)), rgba(var(--tipo), 0.15));
  z-index: 2;
}

/* Cabecera del post: autor + cuándo + categoría */
.hub-post-head {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  padding: 1rem 1.15rem 0.85rem;
}

.hub-post-avatar {
  width: 38px;
  height: 38px;
  border-radius: 50%;
  flex-shrink: 0;
  display: grid;
  place-items: center;
  background: var(--pl-grad);
  color: #12001f;
  font-family: var(--font-heading);
  font-weight: 900;
  font-size: 0.78rem;
  overflow: hidden;
}

.hub-post-avatar img { width: 100%; height: 100%; object-fit: cover; }
.hub-post-who { flex: 1; min-width: 0; line-height: 1.3; }

.hub-post-name {
  margin: 0;
  font-size: 0.88rem;
  font-weight: 800;
  color: var(--pl-text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.hub-post-time { margin: 0; font-size: 0.75rem; color: var(--pl-muted); }

/* Etiqueta del aviso, teñida por --tipo */
.hub-feed-tag {
  align-self: flex-start;
  font-size: 0.7rem;
  font-weight: 800;
  letter-spacing: 0.03em;
  padding: 0.24rem 0.7rem;
  border-radius: 999px;
  background: rgba(var(--tipo), 0.14);
  border: 1px solid rgba(var(--tipo), 0.32);
  color: rgb(var(--tipo));
}

.hub-feed-card-body { padding: 0 1.15rem 0.95rem; display: flex; flex-direction: column; gap: 0.45rem; }

.hub-feed-title {
  font-family: var(--font-heading);
  font-size: 1.08rem;
  line-height: 1.35;
  margin: 0;
  color: var(--pl-text);
}

.hub-feed-desc {
  font-size: 0.9rem;
  line-height: 1.6;
  color: var(--pl-muted);
  margin: 0;
  /* Dos líneas como mucho: en un muro, un párrafo largo mata el ritmo */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* ================================================================
   Tarjeta de JUEGO (actividad publicada)
   ----------------------------------------------------------------
   Un aviso se lee; una actividad se juega. Por eso aquí la portada
   manda: ocupa el ancho, lleva su insignia de tipo y un botón de
   play que crece al pasar por encima, como la ficha de un juego.
   ================================================================ */
/* La portada va justo bajo la cabecera: menos aire entre ambas que en
   un aviso, donde lo que sigue es texto. */
.hub-juego .hub-post-head { padding-bottom: 0.6rem; }
.hub-juego .hub-feed-card-body { padding-top: 0.85rem; }

/* Aro del color del tipo alrededor del avatar del autor */
.hub-juego .hub-post-avatar {
  box-shadow: 0 0 0 2px rgba(var(--tipo), 0.55), 0 0 12px -4px rgba(var(--tipo), 0.9);
}

/* Sello de recién publicado (últimos 3 días) */
.hub-juego-nuevo {
  flex-shrink: 0;
  padding: 0.24rem 0.6rem;
  border-radius: 999px;
  background: rgba(var(--tipo), 0.16);
  border: 1px solid rgba(var(--tipo), 0.5);
  color: rgb(var(--tipo));
  font-family: var(--font-heading);
  font-size: 0.65rem;
  font-weight: 900;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  animation: hubNuevoLatido 2.6s ease-in-out infinite;
}

@keyframes hubNuevoLatido {
  0%, 100% { box-shadow: 0 0 0 0 rgba(var(--tipo), 0.5); }
  55%      { box-shadow: 0 0 0 6px rgba(var(--tipo), 0); }
}

/* El título se enciende con el color del tipo al pasar por la tarjeta */
.hub-juego:hover .hub-feed-title { color: rgb(var(--tipo)); }
.hub-juego .hub-feed-title { transition: color 0.22s ease; }

/* En un aviso manda el texto, así que la etiqueta abre el cuerpo */
.hub-aviso .hub-feed-card-body { padding-top: 0.1rem; }

.hub-juego-cover {
  position: relative;
  display: block;
  margin: 0 0.75rem;
  border-radius: 16px;
  overflow: hidden;
  aspect-ratio: 16 / 9;
  text-decoration: none;
  border: 1px solid rgba(var(--tipo), 0.28);
}

.hub-juego-cover:focus-visible { outline: 2px solid rgb(var(--tipo)); outline-offset: 3px; }

/* Es un <img loading="lazy"> (antes era un background-image, que se
   descarga aunque la tarjeta esté a diez pantallas de distancia). */
.hub-juego-img {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  background-color: rgba(var(--tipo), 0.08);
  transition: transform 0.4s cubic-bezier(0.22, 1, 0.36, 1);
}

/* Sin portada: degradado del color del tipo con su emoji gigante,
   que sigue pareciendo una carátula y no un hueco. */
.hub-juego-img-vacia {
  display: grid;
  place-items: center;
  background:
    radial-gradient(120% 120% at 30% 20%, rgba(var(--tipo), 0.35), transparent 60%),
    linear-gradient(135deg, rgba(var(--tipo), 0.16), rgba(9, 4, 15, 0.6));
}

.hub-juego-emoji { font-size: 3.2rem; opacity: 0.55; filter: saturate(0.9); }

.hub-juego-cover:hover .hub-juego-img { transform: scale(1.06); }

/* Destello diagonal que cruza la portada al pasar por encima. Solo se
   mueve un transform, así que no repinta la imagen. */
.hub-juego-cover::after {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 45%;
  z-index: 3;
  pointer-events: none;
  background: linear-gradient(100deg, transparent, rgba(255, 255, 255, 0.28), transparent);
  transform: translateX(-180%) skewX(-18deg);
  transition: transform 0.75s cubic-bezier(0.22, 1, 0.36, 1);
}

.hub-juego-cover:hover::after { transform: translateX(340%) skewX(-18deg); }

/* Velo inferior: sostiene la insignia sobre cualquier portada */
.hub-juego-velo {
  position: absolute;
  inset: 0;
  background: linear-gradient(0deg, rgba(9, 4, 15, 0.85) 0%, rgba(9, 4, 15, 0.1) 45%, transparent 70%);
}

.hub-juego-tipo {
  position: absolute;
  left: 0.7rem;
  bottom: 0.7rem;
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0.3rem 0.75rem;
  border-radius: 999px;
  background: rgba(9, 4, 15, 0.72);
  border: 1px solid rgba(var(--tipo), 0.55);
  color: rgb(var(--tipo));
  font-size: 0.75rem;
  font-weight: 800;
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
}

/* Botón de play: aparece y crece al pasar por encima de la portada */
.hub-juego-play {
  position: absolute;
  right: 0.7rem;
  bottom: 0.7rem;
  display: grid;
  place-items: center;
  width: 2.6rem;
  height: 2.6rem;
  border-radius: 50%;
  background: rgb(var(--tipo));
  color: #0d0416;
  font-size: 1rem;
  box-shadow: 0 8px 22px -6px rgba(var(--tipo), 0.85);
  transform: scale(0.82);
  opacity: 0.85;
  transition: transform 0.25s cubic-bezier(0.34, 1.56, 0.64, 1), opacity 0.25s ease;
}

.hub-juego-cover:hover .hub-juego-play { transform: scale(1); opacity: 1; }

/* Fichas de datos: se leen como stats de un juego, no como metadatos */
.hub-juego-stats { display: flex; flex-wrap: wrap; gap: 0.4rem; margin-top: 0.15rem; }

.hub-juego-stat {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  padding: 0.25rem 0.6rem;
  border-radius: 8px;
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid rgba(255, 255, 255, 0.08);
  font-size: 0.76rem;
  font-weight: 700;
  color: var(--pl-soft);
}

/* Llamada a la acción a todo el ancho, del color del tipo */
.hub-juego-cta {
  display: block;
  padding: 0.7rem 1rem;
  border-radius: 12px;
  background: linear-gradient(135deg, rgba(var(--tipo), 0.95), rgba(var(--tipo), 0.65));
  color: #0d0416;
  font-family: var(--font-heading);
  font-weight: 900;
  font-size: 0.88rem;
  letter-spacing: 0.02em;
  text-align: center;
  text-decoration: none;
  transition: transform 0.2s ease, box-shadow 0.2s ease;
}

.hub-juego-cta:hover { transform: translateY(-2px); box-shadow: 0 12px 26px -10px rgba(var(--tipo), 0.9); }
.hub-juego-cta:focus-visible { outline: 2px solid rgb(var(--tipo)); outline-offset: 3px; }

.hub-feed-thumb {
  display: block;
  width: 100%;
  height: 240px;
  object-fit: cover;
  background-color: rgba(255, 255, 255, 0.04);
}

.hub-feed-foot { padding: 0.9rem 1.15rem 1rem; }

/* En una actividad el pie lleva dos cosas: el botón de jugar, que se
   queda con todo el ancho que sobre, y las acciones rápidas al lado. */
.hub-feed-foot-juego {
  display: flex;
  align-items: center;
  gap: 0.6rem;
}

.hub-feed-foot-juego .hub-juego-cta { flex: 1; min-width: 0; }

.hub-feed-acciones { display: flex; gap: 0.4rem; flex-shrink: 0; }

/* Guardar y compartir. Mismo tamaño que el alto del botón de jugar para
   que el pie se lea como una sola barra y no como tres piezas sueltas. */
.hub-fav,
.hub-share {
  display: grid;
  place-items: center;
  width: 2.65rem;
  height: 2.65rem;
  padding: 0;
  border-radius: 12px;
  border: 1px solid var(--pl-border);
  background: rgba(255, 255, 255, 0.04);
  color: var(--pl-text);
  font-size: 1.05rem;
  line-height: 1;
  cursor: pointer;
  transition: background 0.2s ease, border-color 0.2s ease, transform 0.2s ease;
}

.hub-fav:hover,
.hub-share:hover { background: rgba(255, 255, 255, 0.09); transform: translateY(-2px); }

.hub-fav:focus-visible,
.hub-share:focus-visible { outline: 2px solid var(--pl-cyan); outline-offset: 3px; }

.hub-fav:disabled { cursor: progress; }

.hub-fav.is-on {
  border-color: rgba(255, 107, 149, 0.55);
  background: rgba(255, 107, 149, 0.14);
}

.hub-fav-ico { display: block; }

/* Latido al guardar. Solo transform, y la clase la quita hub.js. */
.hub-fav.is-pop .hub-fav-ico { animation: hubFavPop 0.42s cubic-bezier(0.3, 1.5, 0.5, 1); }

@keyframes hubFavPop {
  0%   { transform: scale(1); }
  45%  { transform: scale(1.45); }
  100% { transform: scale(1); }
}

.hub-feed-link {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.55rem 1.15rem;
  border-radius: 999px;
  border: 1px solid rgba(141, 238, 255, 0.3);
  background: rgba(141, 238, 255, 0.08);
  color: var(--pl-cyan);
  font-weight: 800;
  font-size: 0.86rem;
  text-decoration: none;
  transition: background 0.2s ease, transform 0.2s ease;
}

.hub-feed-link:hover { background: rgba(141, 238, 255, 0.16); transform: translateY(-2px); }
.hub-feed-link:focus-visible { outline: 2px solid var(--pl-cyan); outline-offset: 3px; }

/* Estados del feed */
.hub-feed-empty,
.hub-feed-end {
  text-align: center;
  color: var(--pl-muted);
  font-size: 0.9rem;
  padding: 2.2rem 1rem 0.5rem;
}

.hub-feed-empty-ico { display: block; font-size: 2.4rem; margin-bottom: 0.6rem; opacity: 0.5; }
.hub-feed-sentinel { height: 1px; }
.hub-feed-skeleton { height: 320px; border-radius: 20px; }

/* Aviso de publicación nueva en tiempo real */
.hub-feed-nuevas {
  position: fixed;
  left: 50%;
  bottom: 1.6rem;
  z-index: 40;
  transform: translate(-50%, 140%);
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  padding: 0.65rem 1.25rem;
  border-radius: 999px;
  border: none;
  background: var(--pl-grad);
  color: #16031f;
  font: inherit;
  font-weight: 800;
  font-size: 0.88rem;
  cursor: pointer;
  box-shadow: 0 16px 36px -12px rgba(169, 106, 231, 0.9);
  transition: transform 0.35s cubic-bezier(0.22, 1, 0.36, 1);
}

.hub-feed-nuevas.is-visible { transform: translate(-50%, 0); }
.hub-feed-nuevas:focus-visible { outline: 2px solid var(--pl-cyan); outline-offset: 3px; }

.hub-feed-card.is-nueva { animation: hubCardIn 0.55s cubic-bezier(0.22, 1, 0.36, 1); }

@keyframes hubCardIn {
  from { transform: translateY(-14px) scale(0.97); opacity: 0; }
  to   { transform: none; opacity: 1; }
}

/* ================================================================
   Botón de volver arriba
   ================================================================ */
.hub-top-btn {
  position: fixed;
  right: 1.4rem;
  bottom: 1.4rem;
  z-index: 35;
  width: 2.9rem;
  height: 2.9rem;
  border-radius: 50%;
  border: 1px solid var(--pl-border);
  background: rgba(17, 10, 26, 0.9);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  color: var(--pl-text);
  font-size: 1.1rem;
  cursor: pointer;
  opacity: 0;
  transform: translateY(16px);
  pointer-events: none;
  transition: opacity 0.28s ease, transform 0.28s ease, border-color 0.2s ease;
}

.hub-top-btn.is-visible { opacity: 1; transform: none; pointer-events: auto; }
.hub-top-btn:hover { border-color: var(--pl-cyan); }
.hub-top-btn:focus-visible { outline: 2px solid var(--pl-cyan); outline-offset: 3px; }

/* ================================================================
   Aviso corto (guardado, enlace copiado, errores)
   ================================================================ */
/* Un fallo al guardar no puede quedarse mudo —el corazón se revertiría
   sin explicación— pero tampoco merece un modal. Se dice abajo, en una
   línea, y se va solo. Sube por encima del botón de volver arriba. */
.hub-toast {
  position: fixed;
  left: 50%;
  bottom: 1.4rem;
  z-index: 40;
  margin: 0;
  max-width: min(90vw, 30rem);
  padding: 0.7rem 1.15rem;
  border-radius: 999px;
  border: 1px solid var(--pl-border);
  background: rgba(17, 10, 26, 0.94);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  color: var(--pl-text);
  font-size: 0.86rem;
  font-weight: 700;
  text-align: center;
  box-shadow: 0 18px 40px -22px rgba(0, 0, 0, 0.9);
  opacity: 0;
  transform: translate(-50%, 130%);
  transition: opacity 0.3s ease, transform 0.34s cubic-bezier(0.22, 1, 0.36, 1);
}

/* Empata con [hidden] por especificidad, así que se declara después */
.hub-toast[hidden] { display: none; }

.hub-toast.is-visible { opacity: 1; transform: translate(-50%, 0); }

/* En móvil el aviso ocupa casi todo el ancho y chocaría con el botón
   de volver arriba, así que se coloca por encima de él. */
@media (max-width: 640px) {
  .hub-toast { bottom: 4.6rem; }
}

/* ================================================================
   Modo bajo rendimiento / menos movimiento
   ================================================================ */
body.perf-low .hub-challenge-card::after,
body.perf-low .hub-skeleton,
body.perf-low .hub-live-dot,
body.perf-low .hub-juego-nuevo,
body.perf-low .hub-fav.is-pop .hub-fav-ico,
body.perf-low .hub-feed-card.is-nueva { animation: none; }

body.perf-low .hub-juego-cover::after { display: none; }

/* Sin latido, el filo cian se queda puesto en vez de desaparecer */
body.perf-low .hub-challenge-card::after { opacity: 0.7; }

body.perf-low .hub-feed-card,
body.perf-low .hub-juego-img,
body.perf-low .hub-juego-play,
body.perf-low .hub-juego-cta,
body.perf-low .hub-mini,
body.perf-low .hub-fav,
body.perf-low .hub-share,
body.perf-low .hub-crear-btn,
body.perf-low .hub-toast,
body.perf-low .hub-chip,
body.perf-low .hub-avatar { transition: none; }

body.perf-low .hub-feed-card:hover,
body.perf-low .hub-juego-cta:hover,
body.perf-low .hub-mini:hover,
body.perf-low .hub-fav:hover,
body.perf-low .hub-share:hover,
body.perf-low .hub-crear-btn:hover,
body.perf-low .hub-avatar:hover { transform: none; }

body.perf-low .hub-juego-cover:hover .hub-juego-img { transform: none; }
body.perf-low .hub-juego-play { transform: scale(1); opacity: 1; }

@media (prefers-reduced-motion: reduce) {
  .hub-skeleton,
  .hub-live-dot,
  .hub-juego-nuevo,
  .hub-fav.is-pop .hub-fav-ico,
  .hub-feed-card.is-nueva,
  .hub-challenge-content.gacha-spin-active,
  .hub-reroll-ico.icon-spin { animation: none; }

  .hub-challenge-card::after { animation: none; opacity: 0.7; }
  .hub-juego-cover::after { display: none; }
  .hub-juego .hub-feed-title { transition: none; }

  .hub-feed-card,
  .hub-juego-img,
  .hub-juego-play,
  .hub-juego-cta,
  .hub-mini,
  .hub-fav,
  .hub-share,
  .hub-crear-btn,
  .hub-toast,
  .hub-chip,
  .hub-avatar,
  .hub-reroll-btn,
  .hub-top-btn,
  .hub-feed-nuevas,
  .hub-feed-link { transition: none; }

  .hub-feed-card:hover,
  .hub-juego-cta:hover,
  .hub-mini:hover,
  .hub-fav:hover,
  .hub-share:hover,
  .hub-crear-btn:hover,
  .hub-feed-link:hover,
  .hub-avatar:hover { transform: none; }

  .hub-juego-cover:hover .hub-juego-img { transform: none; }
  .hub-juego-play { transform: scale(1); opacity: 1; }
}

/* ================================================================
   Móvil
   ================================================================ */
@media (max-width: 640px) {
  .hub-shell { padding-inline: 1rem; }
  .hub-status-card { padding: 1.1rem; gap: 1.1rem; }
  .hub-challenge-card { padding: 1.25rem 1.1rem; }
  .hub-feed-thumb { height: 190px; }
  /* El botón de jugar se queda con su fila y las acciones bajan debajo:
     en 320px, "Jugar ahora" + dos botones no caben sin recortarse. */
  .hub-feed-foot-juego { flex-wrap: wrap; }
  .hub-feed-foot-juego .hub-juego-cta { flex: 1 0 100%; }
  .hub-feed-acciones { width: 100%; }
  .hub-feed-acciones > * { flex: 1; }
  .hub-avatar { width: 54px; height: 54px; }
  .hub-top-btn { right: 1rem; bottom: 1rem; }
  .hub-layout { margin-top: 2rem; }
  .hub-main { gap: 2rem; }

  /* Los filtros dejan de ir pegados: en móvil la barra ya come
     bastante alto y encima taparía la primera tarjeta. */
  .hub-feed-filters {
    position: static;
    border-radius: 16px;
    overflow-x: auto;
    flex-wrap: nowrap;
  }
  .hub-chip { flex-shrink: 0; }
}

/* Tema Phosphor: sin esquinas redondeadas, como el resto del sitio */
body.theme-pixel .hub-status-card,
body.theme-pixel .hub-status-guest,
body.theme-pixel .hub-challenge-card,
body.theme-pixel .hub-feed-card,
body.theme-pixel .hub-card,
body.theme-pixel .hub-mini,
body.theme-pixel .hub-mini-cover,
body.theme-pixel .hub-fav,
body.theme-pixel .hub-share,
body.theme-pixel .hub-crear-btn,
body.theme-pixel .hub-toast,
body.theme-pixel .hub-chip,
body.theme-pixel .hub-feed-filters,
body.theme-pixel .hub-juego-cover,
body.theme-pixel .hub-juego-tipo,
body.theme-pixel .hub-juego-stat,
body.theme-pixel .hub-juego-cta,
body.theme-pixel .hub-feed-tag,
body.theme-pixel .hub-reroll-btn { border-radius: 0; }

/* ================================================================
   RECOMENDADO POR PUZZLE LOVERS — en el muro
   ----------------------------------------------------------------
   El sello lo define styles.css (se usa igual en la galería); aquí
   solo va dónde se coloca y cómo se marca la tarjeta fijada.
   ================================================================ */

/* Rótulo de "fijado": va ARRIBA del todo, antes de la firma, porque lo
   primero que hay que entender de una tarjeta que rompe el orden
   cronológico es por qué está donde está. */
.hub-fijada {
  margin: 0;
  padding: 0.5rem 1.15rem 0;
  font-size: 0.72rem;
  font-weight: 800;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: #d9b3ff;
}

/* Por encima del destello diagonal de la portada, que va en z-index 3 */
.hub-reco-badge {
  position: absolute;
  top: 0.6rem;
  left: 0.6rem;
  z-index: 4;
}

/* La tarjeta recomendada se sale del color de su tipo de puzzle y toma
   el morado de la marca: es la señal de que la eligió el team, no una
   propiedad de la actividad. */
.hub-feed-card.es-reco {
  border-color: rgba(201, 139, 255, 0.55);
  box-shadow: 0 0 26px -12px rgba(201, 139, 255, 0.8);
}

.hub-feed-card.es-reco:hover {
  border-color: rgba(201, 139, 255, 0.85);
  box-shadow: 0 20px 44px -24px rgba(201, 139, 255, 0.85);
}

/* Pie de las tarjetas del rail (de qué semana salió la lista) */
.hub-card-pie {
  margin: 0.7rem 0 0;
  font-size: 0.72rem;
  color: var(--pl-muted);
}

.hub-card-pie[hidden] { display: none; }
