* { box-sizing: border-box; }

[hidden] { display: none !important; }

body {
  margin: 0;
  font-family: var(--font-family);
  /* Tres luces muy sutiles (azul, turquesa, violeta) sobre #081225 —
     regla explícita: mantener el fondo, sin fotografías ni animación. */
  background:
    radial-gradient(circle at 15% 15%, rgba(var(--accent-purple-rgb), 0.14), transparent 35%),
    radial-gradient(circle at 50% 100%, rgba(var(--accent-cyan-rgb), 0.08), transparent 40%),
    radial-gradient(circle at 85% 85%, rgba(var(--accent-violet-rgb), 0.1), transparent 38%),
    var(--bg-page);
  background-attachment: fixed;
  color: var(--text-primary);
}

button {
  font-family: inherit;
  cursor: pointer;
}

input[type="text"],
input[type="password"],
input[type="email"],
select {
  font-family: inherit;
  /* font-size explícito en 1rem (16px): sin esto, los campos heredan el
     tamaño por defecto del navegador para controles de formulario (a veces
     menor a 16px) y iOS hace zoom automático al enfocar — mobile-first. */
  font-size: 1rem;
  min-height: 50px;
  background: var(--input-bg);
  border: 1px solid var(--border-color);
  color: var(--text-primary);
  border-radius: var(--radius-sm);
  padding: 0.6rem 0.8rem;
  transition: border-color 0.18s ease, box-shadow 0.18s ease;
}

input[type="text"]::placeholder,
input[type="password"]::placeholder,
input[type="email"]::placeholder {
  color: var(--text-muted);
}

input[type="text"]:focus,
input[type="password"]:focus,
input[type="email"]:focus,
select:focus {
  outline: none;
  border-color: var(--accent-purple);
  box-shadow: 0 0 0 3px var(--accent-purple-soft);
}

.btn-primary {
  background: var(--accent-purple);
  color: var(--accent-contrast, #fff);
  border: none;
  border-radius: var(--radius-sm);
  padding: 0.7rem 1.2rem;
  font-size: 1rem;
  font-weight: 600;
  white-space: nowrap;
  /* Mobile-first: 52px de alto, fácil de tocar con el pulgar en cualquier
     botón principal de la app, no solo el de entrar. */
  min-height: 52px;
  transition: background 0.18s ease, transform 0.08s ease;
}

.btn-primary:hover {
  background: var(--accent-purple-hover);
}

.btn-primary:active {
  background: var(--accent-purple-active);
}

.btn-primary:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}
.btn-primary:disabled:hover {
  background: var(--accent-purple);
}

.icon-btn {
  background: var(--bg-surface-2);
  color: var(--text-primary);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-sm);
  padding: 0.5rem 0.9rem;
  white-space: nowrap;
  transition: background 0.18s ease, border-color 0.18s ease;
  /* Íconos SVG en vez de emoji (2026-07-24, "íconos más profesionales") —
     centrado real necesario ahora que el contenido es un <svg>, no texto
     que se autocentraba solo. */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.4rem;
}
.icon-btn svg { display: block; flex-shrink: 0; }
.icon-btn:hover svg { color: var(--accent-purple); }

.icon-btn:hover {
  background: var(--bg-elevated);
}

.icon-btn.danger {
  background: var(--accent-danger);
  border-color: var(--accent-danger);
  color: #fff;
}

/* Íconos "premium" de la barra superior (2026-07-25, pedido explícito: "los
   íconos sea más premium") — escopeado a los botones de función del topbar
   (mensajes, amigos, quiz, en línea, muro, mi perfil), no toca el resto de
   usos de .icon-btn en la app (listas de acciones, botones con texto, etc.)
   para no desarmar esos layouts. Círculo con degradado sutil + glow violeta
   al pasar el mouse, mismo acento que ya usan los elementos Premium/VIP. */
.topbar-icon-btn {
  width: 40px;
  height: 40px;
  padding: 0;
  border-radius: 50%;
  background: linear-gradient(145deg, var(--bg-elevated), var(--bg-surface-2));
  border: 1px solid var(--border-soft);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.05), 0 2px 6px rgba(0, 0, 0, 0.25);
  transition: transform 0.18s ease, box-shadow 0.18s ease, border-color 0.18s ease, background 0.18s ease;
}
.topbar-icon-btn svg {
  color: var(--text-secondary);
  transition: color 0.18s ease;
}
.topbar-icon-btn:hover {
  background: linear-gradient(145deg, var(--bg-elevated), var(--accent-violet-soft));
  border-color: rgba(var(--accent-violet-rgb), 0.55);
  box-shadow: 0 0 0 3px rgba(var(--accent-violet-rgb), 0.14), 0 6px 16px rgba(var(--accent-violet-rgb), 0.28);
  transform: translateY(-2px);
}
.topbar-icon-btn:hover svg {
  color: var(--accent-violet);
}
.topbar-icon-btn:active {
  transform: translateY(0) scale(0.95);
}
/* Avatar propio ya trae su propio marco/glow — el hueco entre él y el
   siguiente ícono circular quedaba desparejo con el gap general de
   .status-group, se ajusta puntual acá. */
.topbar-icon-btn + .avatar-frame-wrap,
.avatar-frame-wrap + .topbar-icon-btn {
  margin-left: 0.1rem;
}

/* Topbar */
.topbar {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  background: var(--bg-surface);
  border-bottom: 1px solid var(--border-color);
  padding: 0.7rem 1rem;
  /* Área segura (notch/isla dinámica) en PWA standalone — env() da 0 en
     cualquier otro contexto, así que no cambia nada fuera de ese caso. */
  padding-top: max(0.7rem, env(safe-area-inset-top));
}

/* Logo real del topbar (2026-07-27, pedido explícito) — reemplaza el
   ícono cuadrado viejo (logo.svg) + el texto "VacaWeb Chat" por el mismo
   logotipo ancho que ya se ve en la pantalla de entrada. El PNG fuente
   tiene mucha transparencia arriba/abajo (ver comentario junto a
   .entry-logo-crop) así que se recorta con la misma técnica: la caja
   (topbar-logo-crop) solo deja ver el alto real del contenido, la imagen
   completa va desplazada hacia arriba adentro. height = width * 0.1738,
   top = -width * 0.25 — misma proporción del logo, cualquier ancho nuevo
   solo hay que multiplicar por esos dos factores. */
.topbar-logo-crop {
  width: 150px;
  height: 26.07px;
  overflow: hidden;
  position: relative;
  flex-shrink: 0;
}
.topbar-logo {
  display: block;
  position: absolute;
  top: -37.5px;
  left: 0;
  width: 150px;
}

.status-group {
  margin: 0 0 0 auto;
  display: flex;
  align-items: center;
  gap: 0.6rem;
}

.status-pill {
  font-size: 0.8rem;
  color: var(--text-secondary);
  margin: 0;
}

.avatar {
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-weight: 700;
  color: #fff;
  flex-shrink: 0;
  box-shadow: 0 0 0 2px var(--bg-surface);
  overflow: hidden;
}

.avatar-photo {
  background: var(--bg-elevated);
}

.avatar-photo img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Marco de avatar (2026-07-22) — aro decorativo alrededor de la foto/
   iniciales. El wrapper mide lo mismo que el avatar (para no descuadrar el
   layout de roster/mensajes que ya asumen ese tamaño), pero con overflow
   visible: el marco se dibuja más grande (1.43x, ver avatar.js) y sobresale
   a propósito, como en las apps de referencia. */
.avatar-frame-wrap {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  overflow: visible;
}
.avatar-frame-overlay {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  object-fit: contain;
  pointer-events: none;
}
/* Marco "corona" de la tienda (2026-07-24) — imagen sólida (no un anillo
   con hueco), se apoya arriba del avatar como un sombrero en vez de
   centrarse tapándolo entero. */
.avatar-frame-overlay.avatar-frame-crown {
  top: -8%;
  left: 50%;
  transform: translate(-50%, -55%);
}

/* Aros Premium (2026-07-26, pedido explícito: "que tenga movimiento o algún
   efecto especial") — brillo pulsante suave, no rotación (la corona/detalle
   de arriba del diseño se vería rara girando). */
@keyframes premium-frame-pulse {
  0%, 100% { filter: drop-shadow(0 0 3px rgba(139, 108, 255, 0.55)) brightness(1); transform: translate(-50%, -50%) scale(1); }
  50% { filter: drop-shadow(0 0 11px rgba(139, 108, 255, 0.95)) brightness(1.08); transform: translate(-50%, -50%) scale(1.035); }
}
.avatar-frame-premium {
  animation: premium-frame-pulse 2.4s ease-in-out infinite;
}
@media (prefers-reduced-motion: reduce) {
  .avatar-frame-premium { animation: none; }
}

/* Picker de marcos en el modal de perfil — celdas más grandes que
   .badge-option normal (38px) porque los diseños de los aros tienen mucho
   detalle fino que se pierde a ese tamaño. */
.profile-frame-grid {
  grid-template-columns: repeat(auto-fill, minmax(56px, 1fr));
}

.sidebar-toggle {
  display: none;
  background: transparent;
  border: none;
  color: var(--text-primary);
  font-size: 1.2rem;
  padding: 0.2rem 0.4rem;
}

/* Layout */
.layout {
  display: flex;
  height: calc(100vh - 58px);
  /* Se descuenta también env(safe-area-inset-top): el topbar ahora suma esa
     misma área segura a su padding-top (PWA standalone con notch/isla
     dinámica), así que .layout debe encoger lo mismo que el topbar creció,
     o el fondo de la pantalla (formulario de mensaje, controles) queda
     recortado. En cualquier dispositivo sin notch, env() es 0 y no cambia
     nada. */
  height: calc(100dvh - 58px - env(safe-area-inset-top));
}

/* Layout de escritorio en CSS Grid (2026-07-26, pedido explícito:
   "optimización del layout de escritorio... el chat sea el protagonista").
   .chat-layout convive con .layout (mismas reglas de altura/etc. de arriba,
   .display:grid la pisa acá abajo) — se mantiene .layout por compatibilidad
   con cualquier selector viejo que la use, pero el control real del ancho
   de columnas pasa a esta regla nueva.
   giftsPanelState.js decide el modo (persistent = columna en escritorio
   grande, drawer = superpuesto en mediano/chico/móvil) escribiendo
   data-gifts-mode en este mismo elemento; en drawer, .gifts-panel y
   .gifts-trigger-btn salen del flujo del grid (position:fixed), así que la
   3ra columna directamente no se reserva. */
.chat-layout {
  display: grid;
  grid-template-columns: var(--sidebar-width) minmax(0, 1fr);
  width: 100%;
  min-width: 0;
}
.chat-layout[data-gifts-mode="persistent"] {
  grid-template-columns: var(--sidebar-width) minmax(0, 1fr) var(--gifts-panel-collapsed);
  transition: grid-template-columns 200ms ease;
}
.chat-layout[data-gifts-mode="persistent"][data-gifts-open="true"] {
  grid-template-columns: var(--sidebar-width) minmax(0, 1fr) var(--gifts-panel-expanded);
}

.sidebar {
  width: var(--sidebar-width);
  flex-shrink: 0;
  background: var(--bg-surface);
  border-right: 1px solid var(--border-color);
  padding: 1rem;
  overflow-y: auto;
  overflow-x: hidden;
  /* Scrollbar premium (2026-07-26, pedido explícito; afinado el mismo día:
     track casi invisible, sin scrollbar-gutter para que no quede pareciendo
     una línea azul permanente cuando no hay overflow real) — único
     contenedor real que scrollea la columna izquierda (salas + usuarios en
     línea), nunca más el nativo blanco/grueso. */
  scrollbar-width: thin;
  scrollbar-color: rgba(76, 126, 255, 0.7) rgba(7, 19, 38, 0.03);
  overscroll-behavior: contain;
}
.sidebar::-webkit-scrollbar {
  width: 5px;
}
.sidebar::-webkit-scrollbar-track {
  background: rgba(7, 19, 38, 0.03);
  border-radius: 999px;
}
.sidebar::-webkit-scrollbar-thumb {
  background: rgba(76, 126, 255, 0.65);
  border-radius: 999px;
  border: 1px solid rgba(7, 19, 38, 0.5);
}
.sidebar::-webkit-scrollbar-thumb:hover {
  background: rgba(76, 126, 255, 0.9);
}

#create-room-form {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  margin-bottom: 1rem;
}

.admin-login-toggle {
  display: block;
  width: 100%;
  padding: 0.4rem 0.5rem;
  margin-bottom: 0.5rem;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border-color);
  background: transparent;
  color: var(--text-secondary);
  font-size: 0.75rem;
  cursor: pointer;
}

.admin-login-form {
  display: flex;
  gap: 0.4rem;
  margin-bottom: 0.75rem;
}

.admin-login-form input {
  flex: 1;
  min-width: 0;
}

/* Subtítulo "Chatea sin registro" (2026-07-22, pedido explícito) — debajo
   del título de bienvenida. Bug real reportado (captura marinela.png/2):
   .modal-header es flex con justify-content:space-between (pensado para
   título + botón cerrar a los costados) — el subtítulo, como hermano del
   h3, quedaba al lado en vez de abajo, con las letras encimadas/cortadas en
   pantallas angostas. Se scopea SOLO a este modal (el resto de modal-header
   del proyecto, con botón de cerrar, sigue igual). */
/* Composición logo + título + descripción como un solo bloque centrado
   (2026-07-24, integración visual pedida explícitamente) — antes quedaba
   alineado a la izquierda, con el logo y el título separados por poco
   espacio; ahora todo el header se lee como una sola unidad. */
.entry-modal-backdrop .modal-header {
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  border-bottom: none;
  /* Tarjeta compacta (ajuste fino 2026-07-24): menos padding arriba/abajo
     del que tenía el modal-header genérico. */
  padding: 1.1rem 1.2rem 0.2rem;
}
/* Logo — ajuste fino 2026-07-24: +30% sobre el tamaño anterior (135px/165px
   → 175px/215px), mobile-first (base = móvil, min-width lo agranda en
   escritorio). vacawebcha-transparente.png es el isotipo+wordmark completo
   (no un ícono cuadrado) — se deja la altura automática para no
   deformarlo. Margen inferior reducido a 24px (antes 28px) y el h3 pierde
   su margen por defecto del navegador (ver regla de abajo) para que
   logo+título+descripción se lean como un solo bloque, sin el espacio
   vacío extra que sumaban ambos márgenes together. */
/* Bug real corregido (2026-07-24): vacawebcha-transparente.png es un
   lienzo de 1536x1024 pero el logo real (ícono+texto) solo ocupa una franja
   angosta en el medio — medido con precisión (pixel por pixel, canvas +
   getImageData): el contenido visible va de 384px a 651px de alto (267px
   de 1024, ~26%). El resto es transparencia — a esto se debía el "espacio
   enorme" entre el logo y "Bienvenido": la CAJA de la imagen (con toda su
   transparencia) sí tenía el margen correcto (24px), pero la imagen
   entera ocupaba ~117px de alto aunque el logo visible mide ~30px.
   .entry-logo-crop recorta la caja al tamaño real del contenido visible
   (overflow:hidden + la imagen completa desplazada hacia arriba adentro) —
   los números sos fracciones exactas del ancho (topOffset = -25% del
   ancho, altura recortada = 17.38% del ancho), así escalan solos entre
   los dos tamaños de logo (móvil/escritorio) sin recalcular a mano. */
.entry-logo-crop {
  width: 175px;
  height: 30.42px;
  overflow: hidden;
  position: relative;
  margin: 0 auto 14px;
}
.entry-logo {
  display: block;
  position: absolute;
  top: -43.75px;
  left: 0;
  width: 175px;
  height: auto;
}
@media (min-width: 700px) {
  .entry-logo-crop {
    width: 215px;
    height: 37.37px;
  }
  .entry-logo {
    width: 215px;
    top: -53.75px;
  }
}
.entry-modal-backdrop h3 {
  margin: 0;
  font-size: 1.4rem;
  font-weight: 700;
}
.entry-subtitle {
  margin-top: 0.3rem;
  color: var(--text-secondary);
  font-size: 0.9rem;
}

/* Texto de ayuda bajo el botón principal ("No necesitas registrarte") */
.entry-helper-text {
  margin: 0.6rem 0 0;
  text-align: center;
  /* Ajuste fino 2026-07-24: text-muted se leía como "desactivado" —
     text-secondary es más luminoso y sigue siendo un tono secundario, no
     compite con el título. */
  color: var(--text-secondary);
  font-size: 0.8rem;
}

/* Iniciar sesión con Google (2026-07-24) */
.entry-divider {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 16px 0 12px;
  color: var(--text-muted);
  font-size: 0.78rem;
}
.entry-divider::before,
.entry-divider::after {
  content: '';
  flex: 1;
  height: 1px;
  background: var(--border-color);
}
.entry-google-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  width: 100%;
  min-height: 48px;
  background: var(--bg-elevated);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-sm);
  color: var(--text-primary);
  font-size: 0.9rem;
  font-weight: 600;
  text-decoration: none;
  transition: background 0.18s ease, border-color 0.18s ease;
}
.entry-google-btn:hover {
  background: var(--accent-purple-soft);
  border-color: var(--accent-purple);
}

/* Acciones secundarias — "Iniciar sesión" / "Crear una cuenta", lado a lado
   en escritorio; en móvil se acomodan sin perder el mínimo táctil (44px). */
.entry-secondary-actions {
  display: flex;
  gap: 0.6rem;
}
.entry-secondary-btn {
  flex: 1;
  min-width: 0;
  min-height: 44px;
  padding: 0.5rem 0.8rem;
  /* Ajuste fino 2026-07-24: fondo transparente + borde tenue se leía como
     "desactivado". Con bg-elevated de reposo y borde/texto más presentes
     se distingue como acción real, sin dejar de ser secundario ni de verse
     oscuro (Aurora Premium). */
  background: var(--bg-elevated);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-sm);
  color: var(--text-primary);
  font-size: 0.85rem;
  font-weight: 600;
  cursor: pointer;
  transition: background 0.18s ease, border-color 0.18s ease, transform 0.08s ease;
}
.entry-secondary-btn:hover {
  /* --bg-elevated ya es el tono más claro de la escala Aurora — para un
     hover más luminoso "sin exagerar" se suma un tinte azul translúcido en
     vez de buscar un gris más claro que no existe en la paleta. */
  background: var(--accent-purple-soft);
  border-color: var(--accent-purple);
  color: var(--accent-select);
}
.entry-secondary-btn:active {
  transform: scale(0.98);
}

/* Franja de login/crear cuenta — vive FUERA de .modal-body/.modal-footer,
   como su propia sección al pie del modal. */
.entry-login-section {
  padding: 0.75rem 1.5rem 1rem;
  border-top: 1px solid var(--border-color);
  text-align: center;
}
#entry-login-fields {
  margin-top: 0.5rem;
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}
#entry-login-fields input {
  width: 100%;
}
/* "¿Olvidaste tu contraseña?" (2026-07-24) — mismo criterio visual que ya
   usaba .entry-admin-toggle (retirado en la integración visual de Aurora
   Premium): texto chico, subrayado, sin fondo, no compite con los botones
   reales de la fila de arriba. */
.entry-forgot-link {
  display: block;
  margin-top: 0.5rem;
  padding: 0;
  border: none;
  background: none;
  color: var(--text-secondary);
  font-size: 0.78rem;
  text-decoration: underline;
  cursor: pointer;
}

/* Banner para apartar el nick (2026-07-22, pedido explícito) — barra
   angosta debajo del topbar, no un modal: no debe interrumpir a nadie que
   ya está chateando, solo ofrecer la opción hasta que la cierren o se
   registren. */
/* Bug real reportado (2026-07-22, captura deleter.png en móvil): el banner
   vivía DENTRO del flujo normal (debajo del topbar, empujando .layout hacia
   abajo) — en pantallas angostas el texto pasa a 2 líneas, ocupa más alto
   de lo que .layout (`height: calc(100vh - 58px)`, solo descuenta el
   topbar) esperaba, y el input de mensaje/controles de abajo quedaban
   recortados fuera de la pantalla. Fix: flotante con position:fixed, mismo
   criterio que #vip-toast (ya probado en esta app) — no empuja nada, solo
   se superpone arriba del contenido; z-index menor al de los modales
   (100) para no taparlos si hay uno abierto. */
.claim-nick-banner {
  position: fixed;
  top: 58px;
  left: 0;
  right: 0;
  z-index: var(--z-registration-banner);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem;
  padding: 0.5rem 1rem;
  background: var(--accent-purple-soft);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  border-bottom: 1px solid var(--border-color);
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.25);
  font-size: 0.85rem;
}
.claim-nick-banner span {
  flex: 1;
  min-width: 200px;
  color: var(--text-primary);
}
.claim-nick-banner .btn-primary {
  padding: 0.35rem 0.9rem;
  font-size: 0.82rem;
}

.room-type-toggle {
  display: flex;
  gap: 0.4rem;
}

.room-type-btn {
  flex: 1;
  padding: 0.45rem 0.5rem;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border-color);
  background: var(--bg-surface-2);
  color: var(--text-secondary);
  font-size: 0.78rem;
  font-weight: 600;
  cursor: pointer;
  transition: background 0.12s ease, color 0.12s ease, border-color 0.12s ease;
}

.room-type-btn.active {
  background: var(--accent-purple-soft);
  border-color: var(--accent-select);
  color: var(--text-primary);
}

.room-list {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}

.room-list li {
  padding: 0.55rem 0.6rem 0.55rem 0.5rem;
  border-radius: var(--radius-md);
  border-left: 3px solid transparent;
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 0.6rem;
  color: var(--text-secondary);
  transition: background 0.12s ease, border-color 0.12s ease;
}

.room-list li:hover {
  background: var(--bg-surface-2);
}

/* Barra de acento a la izquierda en la sala activa (2026-07-24, ajuste
   visual pedido) — mismo criterio que la referencia. */
.room-list li.active {
  background: var(--accent-purple-soft);
  border-left-color: var(--accent-purple);
  /* Blanco explícito (2026-07-26, pedido explícito: "letra blanca para que
     contraste con el azul") — antes usaba --text-primary (#f5f8ff, ya casi
     blanco) pero el usuario lo reportó como poco legible sobre el fondo
     morado-azulado de la sala activa; #fff es el máximo contraste posible. */
  color: #fff;
}

.room-name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-weight: 600;
  display: flex;
  align-items: center;
  gap: 0.4rem;
}
.room-type-icon {
  flex-shrink: 0;
  display: inline-flex;
  color: var(--text-muted);
}

.room-list li small {
  color: var(--text-muted);
  white-space: nowrap;
  font-size: 0.72rem;
  display: flex;
  align-items: center;
  gap: 0.3rem;
}
.room-online-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--accent-success);
  flex-shrink: 0;
}

/* Main room column */
.room {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-width: 0;
  min-height: 0;
}

.room-header {
  /* 2026-07-25, pedido explícito: "el chat puede estar más arribita" —
     recorte vertical chico, el horizontal queda igual. */
  padding: 0.6rem 1.2rem;
  border-bottom: 1px solid var(--border-color);
}

/* Bug real reportado (2026-07-26, captura "blanca.png"): este es el título
   de la sala arriba del CHAT (#room-title), un elemento distinto de
   .room-list li.active .room-name (la fila en el panel lateral, ya blanca
   desde antes) — ESTE nunca tenía color propio, heredaba un tono apagado.
   Blanco explícito para que contraste bien con el fondo oscuro. */
.room-header h2 {
  margin: 0;
  font-size: 1.1rem;
  display: flex;
  align-items: center;
  gap: 0.4rem;
  color: #fff;
}

#room-title .room-type-icon {
  color: var(--accent-violet);
}

/* Tira compacta de hablantes (Sala de Audio/Video) — visible en TODAS las
   resoluciones (2026-07-21: antes solo en móvil, ahora las pestañas Video/Chat
   son universales, ver .mobile-tabs abajo) para no perderse quién habla
   mientras se está en la pestaña de Chat. Mostrar/ocultar según haya
   hablantes lo controla el atributo HTML "hidden" desde stage.js (regla
   global [hidden]{display:none!important}); acá solo el layout de cuando
   SÍ está visible. */
.mini-stage-strip {
  /* inline-flex (2026-07-25, bug real reportado con captura silla1aq.png):
     con `display:flex` a secas, el contenedor sin ancho propio se estira a
     ocupar TODO el ancho del header — inofensivo mientras no tenía fondo,
     pero ahora que es un fondo degradado visible quedaba una barra violeta
     enorme casi vacía con un solo círculo perdido a la izquierda. inline-flex
     hace que la píldora se ajuste al contenido real (crece con más
     hablantes), max-width evita que se pase del ancho del header. */
  display: inline-flex;
  max-width: 100%;
  align-items: center;
  gap: 0.4rem;
  overflow-x: auto;
  margin-top: 0.5rem;
  /* 2026-07-25, bug real reportado (captura core.jpeg, móvil y desktop): el
     círculo del hablante se veía con la parte de arriba cortada en seco.
     Causa: `overflow-x:auto` sin un `overflow-y` explícito hace que el
     navegador fuerce overflow-y a `auto` también (regla real del spec de
     CSS Overflow — no se puede scrollear en un eje y dejar el otro
     "visible" sin clip). El marco premium de avatar_frame se dibuja más
     grande que el avatar (hasta 1.67x, ver avatar.js) y sobresale arriba/
     abajo a propósito — con el alto de esta fila ajustado al avatar de
     30px nomás, ese sobrante quedaba recortado por el overflow-y forzado.
     Fix: suficiente padding vertical para que el marco más grande (30px *
     1.67 ≈ 50px, ~10px de sobra arriba y abajo) quepa entero dentro de la
     caja, así el overflow-y:auto nunca tiene nada que recortar. */
  padding: 11px 0.6rem;
  /* "Asientos" premium, segunda vuelta (2026-07-25) — el violeta-morado del
     primer intento "no combinaba" (pedido del usuario). Paleta nueva:
     dorado/negro estilo salón VIP (mismo tono que --accent-gold, ya usado
     en la insignia de corona/diamante VIP de esta app), pendiente de
     reemplazar por una silla ilustrada real cuando el usuario la genere. */
  background: linear-gradient(135deg, #0b0805, #241a08);
  border: 1px solid rgba(var(--accent-gold-rgb), 0.45);
  box-shadow: inset 0 0 16px rgba(var(--accent-gold-rgb), 0.12);
  border-radius: 999px;
}

/* Pestañas Video/Chat (2026-07-21: el nombre quedó de cuando esto era solo
   para móvil — ahora se usan en todas las resoluciones, pedido explícito del
   usuario para no repetir en desktop el problema de que el chat quedaba
   apretado en una cajita chica debajo del video). */
.mobile-tabs {
  display: flex;
  border-bottom: 1px solid var(--border-color);
}

.tab-btn {
  flex: 1;
  background: var(--bg-surface);
  color: var(--text-secondary);
  border: none;
  padding: 0.7rem;
  font-weight: 600;
  border-bottom: 2px solid transparent;
  cursor: pointer;
}

.tab-btn.active {
  color: var(--text-primary);
  border-bottom-color: var(--accent-purple);
}

.room-body {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-width: 0;
  min-height: 0;
  padding: 1rem 1.2rem;
  /* Área segura inferior (home indicator de iPhone en PWA standalone) —
     el formulario de mensaje es el último elemento de este contenedor, así
     que basta con sumarla acá una sola vez en vez de tocar cada barra
     inferior (mensaje, controles de karaoke, etc.) por separado. */
  padding-bottom: max(1rem, env(safe-area-inset-bottom));
  gap: 1rem;
  /* Bug real encontrado y corregido (2026-07-26, pedido explícito: "no
     aplicar overflow:auto simultáneamente al contenedor del chat y a varios
     contenedores hijos" — el scrollbar blanco/nativo reportado). Este
     contenedor y .messages (el hijo real dentro de .chat-panel) tenían
     overflow-y:auto los DOS a la vez, compitiendo. Cada panel ahora maneja
     su propio scroll: .messages ya lo tenía, .video-panel.active lo suma
     acá abajo (antes dependía de este overflow externo, con muchos tiles de
     video podría haber quedado sin scroll), y .karaoke-panel.active ya lo
     tenía desde antes. Este contenedor ya no scrollea. */
}

/* Video/Chat — solo se muestra el panel activo (2026-07-21: antes ambos
   convivían apilados en desktop, dejando el chat apretado en una cajita
   chica debajo del video; ahora se comportan igual que en móvil en todas
   las resoluciones, un panel a pantalla completa por vez). */
.video-panel,
.chat-panel,
.karaoke-panel {
  display: none;
}

.video-panel.active,
.chat-panel.active,
.karaoke-panel.active {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 0;
}

.video-panel.active {
  gap: 0.6rem;
  /* Scroll propio (2026-07-26) — antes dependía del overflow-y:auto de
     .room-body (ya quitado, ver comentario ahí); con muchos tiles de video
     esta es la red de seguridad para no cortar contenido. */
  overflow-y: auto;
  min-width: 0;
}

.karaoke-panel.active {
  gap: 0.6rem;
  overflow-y: auto;
  padding-bottom: 1rem;
}

/* Sala de Karaoke: pantalla única, sin pestañas (2026-07-21, pedido
   explícito con capturas de referencia) — .karaoke-merged se agrega en
   .room-body (stage.js, applyRoomTypeTabs) cuando la sala es de tipo
   karaoke. Fuerza los dos paneles a convivir apilados, ignorando la
   exclusividad normal de .active de arriba (que sigue rigiendo para
   Audio/Video, sin tocar). El video/cola/mic quedan arriba con su tamaño
   natural; el chat real (con emoticones, GIFs, regalos, respuestas) ocupa
   el resto, scrolleable, exactamente como en las apps de karaoke de
   referencia. */
.room-body.karaoke-merged {
  overflow-y: auto;
}
.room-body.karaoke-merged .karaoke-panel,
.room-body.karaoke-merged .chat-panel {
  display: flex !important;
  flex-direction: column;
}
.room-body.karaoke-merged .karaoke-panel {
  flex: 0 0 auto;
  gap: 0.6rem;
}
.room-body.karaoke-merged .chat-panel {
  flex: 1;
  min-height: 320px;
}

.video-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: 0.6rem;
}

.video-tile {
  position: relative;
  background: #000;
  border: 1px solid var(--border-color);
  border-radius: var(--radius-md);
  overflow: hidden;
  aspect-ratio: 4 / 3;
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.35);
}

.video-tile video {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.video-tile .tile-label {
  position: absolute;
  bottom: 6px;
  left: 8px;
  color: #fff;
  font-size: 0.75rem;
  background: rgba(0, 0, 0, 0.45);
  padding: 0.1rem 0.4rem;
  border-radius: var(--radius-sm);
}

/* Cámara apagada (Sala de Video, Fase 3): avatar circular en vez del <video>
   crudo — informado por el peer remoto vía call:media-state, no algo que
   este cliente pueda inferir del stream por su cuenta. */
.tile-avatar-overlay {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--bg-elevated);
}
.tile-avatar-overlay .avatar {
  width: 30% !important;
  height: auto !important;
  aspect-ratio: 1 / 1;
  font-size: 1.4rem !important;
}

/* Anfitrión en grande, resto en una tira chica abajo (Sala de Video, Fase 3).
   Flex-wrap simple (sin contenedor aparte para las tiras chicas): el tile
   del host ocupa el 100% del ancho y fuerza el salto de línea; el resto
   queda en la(s) línea(s) siguientes en fila. */
.video-grid.host-layout {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 0.6rem;
}
.video-grid.host-layout .video-tile.host-tile {
  width: 100%;
  aspect-ratio: 16 / 9;
}
.video-grid.host-layout .video-tile:not(.host-tile) {
  width: 130px;
  aspect-ratio: 4 / 3;
}

.video-empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.3rem;
  text-align: center;
  background: var(--bg-surface);
  border: 1px dashed var(--border-color);
  border-radius: var(--radius-lg);
  padding: 2.5rem 1.5rem;
  color: var(--text-secondary);
}

.video-empty-icon {
  font-size: 2.2rem;
  margin-bottom: 0.3rem;
  opacity: 0.85;
}

.video-empty-state p {
  margin: 0;
  font-weight: 600;
  color: var(--text-primary);
}

.video-empty-state small {
  color: var(--text-muted);
  max-width: 32ch;
}

.call-actions {
  display: flex;
  gap: 0.5rem;
  flex-wrap: wrap;
}

.call-controls {
  display: flex;
  gap: 0.5rem;
  flex-wrap: wrap;
}

/* Sala de Audio (Fase 2) */
.audio-stage {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

/* "Asientos" premium, segunda vuelta (2026-07-25) — el violeta-morado del
   primer intento "no combinaba" (pedido del usuario). Paleta nueva:
   dorado/negro estilo salón VIP (mismo --accent-gold ya usado en la
   insignia de corona/diamante VIP de esta app), cada hablante en su propia
   tarjeta tipo "asiento" de vidrio. Pendiente: reemplazar por una silla
   ilustrada real (el usuario la va a generar) detrás de cada avatar. */
.audio-stage-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: 0.5rem;
  padding: 1rem;
  background: linear-gradient(160deg, #0b0805, #241a08);
  border: 1px solid rgba(var(--accent-gold-rgb), 0.4);
  border-radius: var(--radius-lg);
  min-height: 120px;
}

.stage-tile {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.1rem;
  text-align: center;
}

/* Silla dorada real (2026-07-25, imagen generada por el usuario,
   silladorado.png -- ya viene con transparencia real tanto afuera como en
   el hueco circular del centro, verificado con Python/Pillow, sin
   reprocesar). Medido a mano: el hueco mide ~266px de diámetro dentro del
   lienzo de 1024px, centrado en x=50.05% / y=39.3% -- de ahí sale la escala
   (avatar 56px * 1024/266 ≈ 216px) y el porcentaje de posición del avatar
   adentro. .stage-seat ES del tamaño final de la silla (no un ancla de
   56px) para no tener que calcular offsets negativos a mano. */
.stage-seat {
  position: relative;
  width: 216px;
  height: 216px;
  flex-shrink: 0;
}
.stage-seat-chair {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}
.stage-seat .avatar-with-dot {
  position: absolute;
  left: 50.05%;
  top: 39.3%;
  transform: translate(-50%, -50%);
}

.stage-tile .avatar {
  position: relative;
  box-shadow: 0 0 0 3px rgba(var(--accent-gold-rgb), 0.4), 0 0 0 5px transparent;
  transition: box-shadow 0.15s ease;
}

.stage-tile.speaking .avatar {
  box-shadow: 0 0 0 3px rgba(var(--accent-gold-rgb), 0.4), 0 0 0 5px var(--accent-cyan);
}

/* Punto verde/rojo persistente de estado del mic (2026-07-26) — distinto
   del aro ".speaking" de arriba (ese es transitorio, solo mientras hay
   sonido real detectado). avatar-with-dot es un wrapper propio (no lo
   arma renderAvatarHTML) solo para tener un ancla de tamaño exacto sin
   depender del ancho del .stage-tile completo. */
.avatar-with-dot {
  position: relative;
  display: inline-block;
  line-height: 0;
}
.mic-status-dot {
  position: absolute;
  bottom: 0;
  right: 0;
  width: 13px;
  height: 13px;
  border-radius: 50%;
  border: 2px solid var(--bg-surface);
  box-sizing: content-box;
}
.mic-status-dot.mic-dot-on { background: var(--accent-success); }
.mic-status-dot.mic-dot-off { background: var(--accent-danger); }
.mini-avatar .mic-status-dot { width: 10px; height: 10px; }
.video-tile .mic-status-dot { width: 14px; height: 14px; bottom: 8px; right: 8px; }

/* Estado "Activo/Ocupado" del usuario (2026-07-24, pedido explícito) — mismo
   wrapper .avatar-with-dot y mismas medidas que .mic-status-dot de arriba,
   solo cambia qué representa el color (disponibilidad elegida a mano, no
   estado del micrófono). */
.presence-dot {
  display: inline-block;
  width: 11px;
  height: 11px;
  border-radius: 50%;
  box-sizing: content-box;
  flex-shrink: 0;
}
.presence-dot.presence-active { background: var(--accent-success); }
.presence-dot.presence-busy { background: var(--accent-danger); }
/* Sobre el avatar (roster): posicionado en la esquina, con borde para
   separarse del avatar de abajo — mismo criterio que .mic-status-dot. */
.avatar-with-dot .presence-dot {
  position: absolute;
  bottom: 0;
  right: 0;
  border: 2px solid var(--bg-surface);
}
/* Suelto en el picker del modal de perfil: sin posición absoluta, solo el
   puntito de color junto al texto del botón. */
.presence-toggle {
  display: flex;
  gap: 0.6rem;
  margin-bottom: 0.9rem;
}
.presence-toggle-btn {
  flex: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.4rem;
  padding: 0.5rem 0.7rem;
  background: var(--bg-surface-2);
  color: var(--text-primary);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: background 0.18s ease, border-color 0.18s ease;
}
.presence-toggle-btn:hover { background: var(--bg-elevated); }
.presence-toggle-btn.selected {
  border-color: var(--accent-select);
  background: var(--bg-elevated);
}

.stage-tile .stage-tile-name {
  font-size: 0.75rem;
  font-weight: 600;
  /* Blanco fijo (2026-07-25): antes tomaba var(--text-secondary) para el
     fondo oscuro liso de siempre -- sobre el degradado violeta-morado
     nuevo de .audio-stage-grid, ese gris quedaba con poco contraste. */
  color: #fff;
  max-width: 90px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.stage-tile .stage-tile-icons {
  display: flex;
  gap: 0.2rem;
  font-size: 0.7rem;
}

.mini-stage-strip .mini-avatar {
  position: relative;
  flex-shrink: 0;
  /* Aro oscuro neutro (2026-07-25) — antes usaba var(--bg-surface), pensado
     para mimetizarse con el fondo oscuro liso de siempre; con el fondo
     dorado/negro nuevo, ese azul marino desentonaba un poco. */
  box-shadow: 0 0 0 2px #100b06, 0 0 0 3px transparent;
  transition: box-shadow 0.15s ease;
}
.mini-stage-strip .mini-avatar.speaking {
  box-shadow: 0 0 0 2px #100b06, 0 0 0 3px var(--accent-cyan);
}

#mini-mic-btn.pending {
  color: var(--accent-gold);
}
#mini-mic-btn.speaking-off {
  color: var(--accent-danger);
}

.audio-audience-count {
  margin: 0;
  font-size: 0.8rem;
  color: var(--text-muted);
  text-align: center;
}

.stage-actions {
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
  gap: 0.7rem;
}

/* Mismo degradado violeta→morado de la tarjeta "Hazte Premium" (2026-07-26,
   pedido explícito) — para que estos botones se vean igual de vistosos. */
.stage-cta-btn {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  background: linear-gradient(135deg, var(--accent-violet), var(--accent-purple));
}
.stage-cta-btn:hover { filter: brightness(1.1); }

.pending-hand-note {
  margin: 0;
  font-size: 0.85rem;
  color: var(--text-secondary);
  font-style: italic;
}

.host-pending-hands {
  background: var(--bg-surface-2);
  border-radius: var(--radius-md);
  padding: 0.75rem 1rem;
}

.host-pending-hands h4 {
  margin: 0 0 0.5rem;
  font-size: 0.85rem;
  color: var(--text-primary);
}

.pending-hands-list {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.pending-hand-row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.pending-hand-row .pending-hand-name {
  flex: 1;
  font-size: 0.85rem;
  color: var(--text-primary);
}

/* Chat — display real (flex/none) lo controla el bloque de arriba junto
   con .video-panel, según cuál pestaña está activa. */
.chat-panel {
  flex: 1;
  flex-direction: column;
  min-height: 200px;
}

.messages {
  flex: 1;
  min-width: 0;
  overflow-y: auto;
  overflow-x: hidden;
  background: var(--bg-surface);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-md);
  padding: 0.9rem;
  margin-bottom: 0.6rem;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.02);
  /* Scrollbar premium (2026-07-26, pedido explícito) — único contenedor
     real de mensajes, ver comentario de .room-body sobre el bug del
     scrollbar blanco/nativo (dos overflow:auto compitiendo). */
  scrollbar-width: thin;
  scrollbar-color: rgba(76, 126, 255, 0.72) rgba(7, 19, 38, 0.12);
  scrollbar-gutter: stable;
  overscroll-behavior: contain;
}
.messages::-webkit-scrollbar {
  width: 6px;
}
.messages::-webkit-scrollbar-track {
  background: rgba(7, 19, 38, 0.12);
  border-radius: 999px;
}
.messages::-webkit-scrollbar-thumb {
  background: rgba(76, 126, 255, 0.65);
  border-radius: 999px;
  border: 1px solid rgba(7, 19, 38, 0.55);
}
.messages::-webkit-scrollbar-thumb:hover {
  background: rgba(76, 126, 255, 0.9);
}
.messages::-webkit-scrollbar-button {
  display: none;
  height: 0;
  width: 0;
}

.msg {
  display: flex;
  align-items: flex-start;
  gap: 0.7rem;
  margin: 0.85rem 0;
}

.msg-body {
  max-width: 75%;
  min-width: 0;
}

.msg-body strong {
  display: flex;
  align-items: center;
  font-size: 0.85rem;
  margin-bottom: 0.3rem;
}

.msg-body p {
  margin: 0;
  display: inline-block;
  background: var(--bg-surface-2);
  color: var(--text-primary);
  padding: 0.5rem 0.75rem;
  border-radius: var(--radius-md);
  line-height: 1.4;
  word-break: break-word;
}

.message-form {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  background: var(--bg-surface-2);
  border: 1px solid var(--border-color);
  border-radius: 999px;
  padding: 0.35rem 0.4rem 0.35rem 0.6rem;
}

.message-form input {
  flex: 1;
  min-width: 0;
  background: transparent;
  border: none;
  padding: 0.4rem 0.2rem;
}
.message-form input:focus { outline: none; box-shadow: none; }

.message-form .send-btn {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  border-radius: 999px;
  padding: 0.5rem 1.1rem;
}

/* Solo para móvil/tablet — en desktop ya está el panel de regalos completo
   al costado (ver responsive.css para el breakpoint que lo muestra). */
.mobile-gift-btn { display: none; }
.mobile-gift-btn svg { color: var(--accent-gold); }

/* Chat enriquecido (Fase 4): menciones, respuestas, reacciones */
.mention {
  background: var(--accent-purple-soft);
  color: var(--accent-purple);
  border-radius: 3px;
  padding: 0 0.2rem;
  font-weight: 600;
}

.msg {
  position: relative;
}
.msg-actions {
  display: flex;
  gap: 0.15rem;
  align-self: flex-start;
  opacity: 0.6;
}
.msg-actions .icon-btn {
  padding: 0.25rem;
  font-size: 0.75rem;
}
.msg-actions:hover {
  opacity: 1;
}

.reply-quote {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
  background: var(--bg-elevated);
  border-left: 3px solid var(--accent-purple);
  border-radius: var(--radius-sm);
  padding: 0.3rem 0.5rem;
  margin-bottom: 0.3rem;
  font-size: 0.75rem;
  max-width: fit-content;
}
.reply-quote-name {
  font-weight: 700;
  color: var(--accent-purple);
}
.reply-quote-text {
  color: var(--text-secondary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 40ch;
}

.reaction-bar {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem;
  margin-top: 0.3rem;
}
.reaction-pill {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  background: var(--bg-elevated);
  border: 1px solid var(--border-color);
  border-radius: 999px;
  padding: 0.1rem 0.5rem;
  font-size: 0.75rem;
  color: var(--text-secondary);
  cursor: pointer;
}
.reaction-pill img {
  width: 14px;
  height: 14px;
}
.reaction-pill.mine {
  background: var(--accent-purple-soft);
  border-color: var(--accent-purple);
  color: var(--accent-purple);
}

.reaction-picker {
  display: flex;
  gap: 0.3rem;
  background: var(--bg-elevated);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-md);
  padding: 0.35rem;
  margin-top: 0.3rem;
  width: fit-content;
}
.reaction-picker-option {
  background: none;
  border: none;
  cursor: pointer;
  padding: 0.2rem;
  border-radius: var(--radius-sm);
}
.reaction-picker-option:hover {
  background: var(--bg-surface-2);
}
.reaction-picker-option img {
  width: 20px;
  height: 20px;
}

.reply-preview {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  background: var(--bg-elevated);
  border-radius: var(--radius-sm);
  padding: 0.4rem 0.6rem;
  margin-bottom: 0.4rem;
  font-size: 0.78rem;
  color: var(--text-secondary);
}
#reply-preview-text {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Panel de regalos — colapsable/drawer (2026-07-26, rediseño completo). Dos
   modos posibles seteados por giftsPanelState.js vía data-gifts-mode en
   #chat-layout, este selector reacciona a ambos:
   - persistent (escritorio grande): columna normal del grid, ancho fijo por
     --gifts-panel-expanded, se puede colapsar a --gifts-panel-collapsed.
   - drawer (escritorio mediano/tablet/móvil): sale del flujo del grid,
     overlay position:fixed que se desliza desde la derecha. */
.gifts-panel {
  grid-column: 3;
  grid-row: 1;
  width: 100%;
  min-width: 0;
  display: flex;
  flex-direction: column;
  background: var(--bg-surface);
  border-left: 1px solid var(--border-color);
}
.chat-layout[data-gifts-mode="persistent"][data-gifts-open="false"] .gifts-panel {
  display: none;
}
.chat-layout[data-gifts-mode="drawer"] .gifts-panel {
  position: fixed;
  /* Empieza debajo del topbar (58px), igual que .gifts-drawer-backdrop —
     así nunca tapa los íconos/controles del header. */
  top: 58px;
  right: 0;
  bottom: 0;
  z-index: var(--z-drawer);
  width: var(--gifts-drawer-width);
  border-left: 1px solid var(--border-soft);
  box-shadow: -8px 0 28px rgba(0, 0, 0, 0.4);
  transform: translateX(100%);
  transition: transform 220ms ease;
}
.chat-layout[data-gifts-mode="drawer"][data-gifts-open="true"] .gifts-panel {
  transform: translateX(0);
}
.chat-layout[data-gifts-mode="drawer"][data-gifts-open="false"] .gifts-panel {
  pointer-events: none;
}
@media (prefers-reduced-motion: reduce) {
  .gifts-panel { transition: none; }
}

/* Grupo flotante de utilidades (2026-07-26, pedido explícito: amigos +
   login de admin sacados de la barra superior, "arribita" del disparador
   de regalos) — franja vertical fija en el borde derecho, encima de donde
   suele estar #gifts-trigger-btn. z-index modesto: por encima del
   contenido normal, bien por debajo de drawers/modales (8000+). */
.utility-icon-stack {
  position: fixed;
  top: 70px;
  right: 8px;
  z-index: 40;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}
/* 44x44 (2026-07-26, pedido explícito) — override puntual solo acá, sin
   tocar .topbar-icon-btn base (40x40, usado en el header) para no
   desparejar esos otros botones. */
.utility-icon-btn {
  width: 44px;
  height: 44px;
}

/* Disparador compacto (2026-07-26, pedido explícito: "control premium y
   reconocible... no usar un emoji") — visible cuando el panel está
   colapsado (persistent) o cerrado (drawer). En persistent ocupa la misma
   celda de grid que .gifts-panel (así la columna angosta de 56px lo
   contiene); en drawer se ancla fijo al borde derecho de la pantalla. */
.gifts-trigger-btn {
  grid-column: 3;
  grid-row: 1;
  display: none;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.3rem;
  min-width: 44px;
  min-height: 44px;
  width: 100%;
  height: 100%;
  padding: 0.6rem 0;
  background: var(--bg-elevated);
  border: none;
  border-left: 1px solid rgba(var(--accent-purple-rgb), 0.35);
  color: var(--text-secondary);
  cursor: pointer;
  position: relative;
  transition: background 0.15s ease, color 0.15s ease;
}
.chat-layout[data-gifts-mode="persistent"][data-gifts-open="false"] .gifts-trigger-btn {
  display: flex;
}
/* Unificado visualmente con #utility-icon-stack (2026-07-26, pedido
   explícito: "controles flotantes del lado derecho... unificarlos como una
   pequeña barra vertical premium") — mismo tamaño 44x44 y mismo right:8px
   que .utility-icon-btn, apilado justo debajo en vez de flotar centrado
   solo por su cuenta. Sigue siendo un elemento propio (no se movió dentro
   del stack en el DOM): moverlo de contenedor rompería su otro modo,
   [data-gifts-mode="persistent"], que depende de ser hijo directo de
   .chat-layout para su grid-column/grid-row — ver comentario ahí arriba. */
.chat-layout[data-gifts-mode="drawer"] .gifts-trigger-btn {
  display: flex;
  position: fixed;
  top: 174px;
  right: 8px;
  grid-column: auto;
  grid-row: auto;
  width: 44px;
  height: 44px;
  padding: 0;
  border-radius: 50%;
  background: linear-gradient(145deg, var(--bg-elevated), var(--bg-surface-2));
  border: 1px solid var(--border-soft);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.05), 0 2px 6px rgba(0, 0, 0, 0.25);
}
.chat-layout[data-gifts-mode="drawer"][data-gifts-open="true"] .gifts-trigger-btn {
  display: none;
}
.gifts-trigger-btn:hover {
  background: var(--bg-surface-hover);
  color: var(--text-primary);
}
.gifts-trigger-badge {
  position: absolute;
  top: 6px;
  right: 8px;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--accent-gold);
  box-shadow: 0 0 0 2px var(--bg-elevated);
}

/* Backdrop del drawer (2026-07-26) — "muy sutil", solo sobre el contenido
   central (chat), nunca sobre la columna de salas ni el header. Se ancla
   empezando después de --sidebar-width en vez de cubrir toda la pantalla. */
/* Usa el atributo `hidden` para mostrarse/ocultarse (mismo criterio que
   TODOS los backdrops de esta app, así modalScrollLock.js lo observa sin
   tocarlo) — animation en vez de transition porque hidden→visible es un
   salto duro de display:none, transition no tiene "antes" del que animar
   en ese mismo frame (mismo motivo que las animaciones de entrada de los
   modales del rediseño Midnight Royal, más abajo en este archivo). */
.gifts-drawer-backdrop {
  position: fixed;
  top: 58px;
  left: var(--sidebar-width);
  right: 0;
  bottom: 0;
  z-index: var(--z-drawer-backdrop);
  background: rgba(7, 19, 38, 0.35);
  pointer-events: auto;
}
@media (prefers-reduced-motion: no-preference) {
  .gifts-drawer-backdrop {
    animation: gifts-backdrop-fade-in 200ms ease;
  }
}
@keyframes gifts-backdrop-fade-in {
  from { opacity: 0; }
  to { opacity: 1; }
}
@media (max-width: 767px) {
  .gifts-drawer-backdrop {
    left: 0;
  }
}

.gifts-panel-header {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.9rem 1rem;
  border-bottom: 1px solid var(--border-color);
}
.gifts-title {
  margin: 0;
  font-size: 1rem;
  color: var(--text-primary);
  display: flex;
  align-items: center;
  gap: 0.45rem;
  flex: 1;
  min-width: 0;
}
.gifts-title svg { color: var(--accent-gold); flex-shrink: 0; }
.gifts-panel-coins {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  font-size: 0.78rem;
  color: var(--accent-gold);
  font-weight: 600;
  white-space: nowrap;
}

/* Único contenedor que scrollea dentro del panel (2026-07-26, mismo
   criterio que .messages: header y botón de enviar quedan fijos afuera). */
.gift-grid-scroll {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  overflow-x: hidden;
  padding: 1rem 1rem 0;
  scrollbar-width: thin;
  scrollbar-color: rgba(76, 126, 255, 0.72) rgba(7, 19, 38, 0.12);
  scrollbar-gutter: stable;
  overscroll-behavior: contain;
}
.gift-grid-scroll::-webkit-scrollbar { width: 6px; }
.gift-grid-scroll::-webkit-scrollbar-track { background: rgba(7, 19, 38, 0.12); border-radius: 999px; }
.gift-grid-scroll::-webkit-scrollbar-thumb {
  background: rgba(76, 126, 255, 0.65);
  border-radius: 999px;
  border: 1px solid rgba(7, 19, 38, 0.55);
}
.gift-grid-scroll::-webkit-scrollbar-thumb:hover { background: rgba(76, 126, 255, 0.9); }

.gift-grid {
  display: grid;
  /* Cuadrícula compacta por ancho real del panel (pedido explícito: "panel
     240-280px: 4 columnas; drawer 290-340px: 4 o 5"), no un auto-fill que
     podía dar cualquier número de columnas según el ancho del momento. */
  grid-template-columns: repeat(4, 1fr);
  gap: 0.55rem;
  padding-bottom: 1rem;
}
.chat-layout[data-gifts-mode="drawer"] .gift-grid {
  grid-template-columns: repeat(auto-fill, minmax(60px, 1fr));
}

.gift-option {
  position: relative;
  background: var(--bg-surface-2);
  /* Bordes más sutiles + sin brillo por default (pedido explícito: "reducir
     el ruido visual... evitar que todas las tarjetas tengan un brillo
     fuerte") — antes .selected era la única variante, ahora el estado
     normal también se atenúa a propósito. */
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-md);
  padding: 0.4rem;
  display: flex;
  align-items: center;
  justify-content: center;
  aspect-ratio: 1;
  transition: transform 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease, background 0.15s ease;
}
.gift-option:hover {
  background: var(--bg-elevated);
  border-color: var(--border-color);
  transform: translateY(-1px);
  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.25);
}
.gift-option img { width: 100%; height: 100%; object-fit: contain; }
/* Seleccionar-y-confirmar (2026-07-26) — el regalo elegido se resalta acá,
   el envío real pasa por .gift-send-btn de abajo. Marca de selección chica
   (además del color) para no depender solo del color, pedido explícito de
   accesibilidad. */
.gift-option.selected {
  background: rgba(var(--accent-purple-rgb), 0.14);
  border-color: var(--accent-purple);
  box-shadow: 0 0 0 1px var(--accent-purple);
}
.gift-option.selected::after {
  content: '✓';
  position: absolute;
  top: -6px;
  right: -6px;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--accent-purple);
  color: #fff;
  font-size: 0.6rem;
  font-weight: 700;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 0 0 2px var(--bg-surface);
}
.gift-send-btn {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  /* Padding inferior del panel (pedido explícito: "el botón no debe cubrir
     la última fila, padding inferior suficiente al contenedor
     desplazable") — el botón es hijo directo de .gifts-panel, no de
     .gift-grid-scroll, así que ya está FUERA del área que scrollea; este
     margen es solo para no quedar pegado a los bordes del panel. */
  margin: 0.8rem 1rem max(0.8rem, env(safe-area-inset-bottom));
  width: auto;
  border: none;
  border-radius: var(--radius-md);
  padding: 0.75rem 0.9rem;
  /* "Estado habilitado azul principal" (pedido explícito) — antes era un
     degradado violeta-morado; se deja liso, el mismo azul de acento
     principal de la app. */
  background: var(--accent-purple);
  color: #fff;
  font-weight: 700;
  font-size: 0.88rem;
  cursor: pointer;
  transition: filter 0.15s ease, opacity 0.15s ease;
  overflow: hidden;
}
.gift-send-btn span {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.gift-send-btn:hover:not(:disabled) { filter: brightness(1.1); }
.gift-send-btn:disabled {
  background: var(--bg-elevated);
  color: var(--text-muted);
  opacity: 1;
  cursor: default;
}
.gift-price {
  position: absolute;
  left: 50%;
  bottom: -0.5rem;
  transform: translateX(-50%);
  /* "Cápsula oscura, reducir la saturación de la amarilla actual" (pedido
     explícito) — antes era dorado sólido con texto oscuro (mucho brillo);
     ahora es oscura con texto dorado, mismo criterio que .store-item-price
     del rediseño de la Tienda. */
  background: rgba(7, 19, 38, 0.85);
  border: 1px solid rgba(var(--accent-gold-rgb), 0.4);
  color: var(--accent-gold);
  font-size: 0.62rem;
  font-weight: 700;
  line-height: 1;
  padding: 0.18rem 0.4rem;
  border-radius: 999px;
  white-space: nowrap;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.35);
}

/* Comprar monedas (Fase C, 2026-07-24) */
.coin-store-modal { max-width: 480px; }
.coin-store-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
  gap: 0.75rem;
}
.coin-package-card {
  background: var(--bg-surface-2);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-md);
  padding: 1rem 0.8rem;
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.5rem;
}
.coin-package-amount {
  font-size: 1.2rem;
  font-weight: 700;
  color: var(--accent-gold);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.1rem;
}
.coin-package-amount span {
  font-size: 0.7rem;
  font-weight: 500;
  color: var(--text-muted);
}
.coin-package-price {
  color: var(--text-secondary);
  font-size: 0.9rem;
}
.coin-package-buy-btn {
  width: 100%;
  border: none;
  border-radius: var(--radius-sm);
  padding: 0.5rem 0.8rem;
  background: var(--accent-purple);
  color: #fff;
  font-weight: 600;
  cursor: pointer;
  transition: background 0.15s ease;
}
.coin-package-buy-btn:hover { background: var(--accent-purple-hover); }
.coin-package-buy-btn:disabled { opacity: 0.6; cursor: default; }
.coin-store-error {
  background: rgba(var(--accent-danger-rgb), 0.15);
  color: var(--accent-danger);
  border-radius: var(--radius-sm);
  padding: 0.5rem 0.7rem;
  font-size: 0.82rem;
  margin: 0 0 0.8rem;
}

/* Header con botón "volver" opcional (coin-store-back-btn) — el título
   crece para ocupar el espacio libre en vez de quedar pegado al botón. */
.coin-store-modal .modal-header h3 { flex: 1; }

/* Formulario de pago embebido con Stripe Elements (Fase C/D, 2026-07-26) */
.payment-embed-summary {
  text-align: center;
  color: var(--text-secondary);
  font-size: 0.92rem;
  font-weight: 600;
  margin: 0 0 1rem;
}
.payment-embed-view { display: flex; flex-direction: column; gap: 1rem; }
.payment-embed-pay-btn {
  width: 100%;
  border: none;
  border-radius: var(--radius-sm);
  padding: 0.7rem 0.8rem;
  background: var(--accent-purple);
  color: #fff;
  font-weight: 700;
  font-size: 0.95rem;
  cursor: pointer;
  transition: background 0.15s ease;
}
.payment-embed-pay-btn:hover { background: var(--accent-purple-hover); }
.payment-embed-pay-btn:disabled { opacity: 0.6; cursor: default; }

.premium-benefits-list {
  margin: 0 0 1rem;
  padding: 0 0 0 1.2rem;
  color: var(--text-secondary);
  font-size: 0.9rem;
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}

/* Fila horizontal tipo "log" para regalo enviado (2026-07-24, ajuste
   visual pedido) — icono + texto en línea, en vez de columna centrada;
   la franja resaltada la da .msg:has(.gift-message) sobre toda la fila. */
.gift-message {
  display: inline-flex !important;
  flex-direction: row;
  align-items: center;
  gap: 0.5rem;
  background: transparent !important;
  padding: 0 !important;
  font-size: 0.85rem;
}
.gift-message-img { width: 30px; height: 30px; object-fit: contain; }
.msg:has(.gift-message) {
  background: var(--bg-surface-2);
  border-radius: var(--radius-md);
  padding: 0.5rem 0.7rem;
}

/* Entrada más notoria (2026-07-22, pedido explícito) — más larga, con más
   rebote y un poco de caída/giro de más, en vez del pop simple de antes. */
@keyframes gift-pop {
  0%   { transform: scale(0) rotate(-30deg) translateY(24px); opacity: 0; }
  35%  { transform: scale(1.45) rotate(12deg) translateY(-10px); opacity: 1; }
  55%  { transform: scale(0.85) rotate(-8deg) translateY(6px); }
  72%  { transform: scale(1.15) rotate(5deg) translateY(-4px); }
  86%  { transform: scale(0.96) rotate(-2deg) translateY(1px); }
  100% { transform: scale(1) rotate(0) translateY(0); }
}
.gift-pop {
  animation: gift-pop 1s cubic-bezier(.25, 1.5, .5, 1) both;
}

@keyframes gift-glow {
  0%, 100% { filter: drop-shadow(0 0 2px rgba(var(--accent-gold-rgb), 0.4)); }
  50% { filter: drop-shadow(0 0 12px rgba(var(--accent-gold-rgb), 0.9)); }
}
.gift-pop-special {
  animation: gift-pop 1s cubic-bezier(.25, 1.5, .5, 1) both, gift-glow 1.4s ease-in-out 1s 2;
}

.chat-emoticon {
  width: 20px;
  height: 20px;
  vertical-align: middle;
  object-fit: contain;
}

/* Selector de emoticones — dos pestañas: estáticos y animados */
.emoticon-picker {
  margin-bottom: 0.5rem;
  background: var(--bg-surface);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-md);
  overflow: hidden;
}
.emoticon-tabs {
  display: flex;
  border-bottom: 1px solid var(--border-color);
}
.emoticon-tab {
  flex: 1;
  background: none;
  border: none;
  padding: 0.5rem;
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--text-muted);
  cursor: pointer;
}
.emoticon-tab.active {
  color: var(--text-primary);
  background: var(--bg-surface-2);
}
.emoticon-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(44px, 1fr));
  gap: 0.3rem;
  max-height: 160px;
  overflow-y: auto;
  padding: 0.6rem;
}
.emoticon-option {
  background: none;
  border: none;
  padding: 0.2rem;
  border-radius: var(--radius-sm);
  display: flex;
  align-items: center;
  justify-content: center;
}
.emoticon-option:hover { background: var(--bg-surface-2); }
.emoticon-option img { width: 24px; height: 24px; object-fit: contain; }

.sidebar-backdrop {
  display: none;
}

/* Insignia/mascota junto al nombre, dentro de mensajes y roster */
.name-badge {
  width: 18px;
  height: 18px;
  object-fit: contain;
  vertical-align: middle;
  margin-left: 0.25rem;
}

/* Diamante VIP (2026-07-22) — ícono de rango ANTES del nombre, en mensajes,
   roster y perfil. */
.vip-badge {
  width: 26px;
  height: 26px;
  object-fit: contain;
  vertical-align: middle;
  margin-right: 0.3rem;
}

/* Insignia VIP/Premium propia del usuario (2026-07-26) — vip-normal.png es
   una píldora ancha (relación 3:1), vip-premier.png un medallón cuadrado;
   por eso cada una tiene su propia altura en vez de compartir un tamaño. */
.tier-badge {
  vertical-align: middle;
  object-fit: contain;
  margin-left: 0.3rem;
}
.tier-badge-vip { height: 22px; width: auto; }
.tier-badge-premium { height: 28px; width: auto; }

/* Aviso flotante de recompensa VIP (2026-07-22) — independiente de cualquier
   sala/panel porque puede dispararse antes de unirse a ninguna (justo al
   completar la pantalla de entrada). z-index por encima de los modales
   (200) para que se vea aunque justo se esté cerrando uno. */
#vip-toast {
  position: fixed;
  top: 1rem;
  left: 50%;
  transform: translate(-50%, -140%);
  z-index: var(--z-toast);
  display: flex;
  align-items: center;
  gap: 0.6rem;
  background: linear-gradient(135deg, var(--bg-elevated), var(--bg-page));
  border: 1px solid rgba(var(--accent-violet-rgb), 0.4);
  border-radius: 999px;
  padding: 0.5rem 1rem 0.5rem 0.5rem;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4), 0 0 24px rgba(var(--accent-violet-rgb), 0.18);
  color: var(--text-on-accent);
  font-size: 0.85rem;
  max-width: 90vw;
  opacity: 0;
  transition: transform 0.4s ease, opacity 0.4s ease;
  pointer-events: none;
}
#vip-toast.vip-toast-show {
  transform: translate(-50%, 0);
  opacity: 1;
}
#vip-toast img {
  width: 34px;
  height: 34px;
  object-fit: contain;
  filter: drop-shadow(0 0 6px rgba(var(--accent-violet-rgb), 0.6));
}

/* Mensajito de monedas ganadas/gastadas (2026-07-26, pedido explícito) —
   pila en la esquina inferior derecha (no choca con #vip-toast, que vive
   arriba centrado), más de uno se apila con column-reverse en vez de
   reemplazarse. */
#coin-toast-stack {
  position: fixed;
  bottom: 1.2rem;
  right: 1.2rem;
  z-index: var(--z-toast);
  display: flex;
  flex-direction: column-reverse;
  gap: 0.5rem;
  pointer-events: none;
  max-width: min(320px, calc(100vw - 2.4rem));
}
.coin-toast {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  background: linear-gradient(135deg, var(--bg-elevated), var(--bg-page));
  border: 1px solid var(--border-color);
  border-radius: var(--radius-md);
  padding: 0.6rem 0.9rem;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
  color: var(--text-primary);
  font-size: 0.82rem;
  opacity: 0;
  transform: translateY(8px);
  transition: transform 0.3s ease, opacity 0.3s ease;
}
.coin-toast.coin-toast-show { opacity: 1; transform: translateY(0); }
.coin-toast-amount {
  flex-shrink: 0;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.coin-toast-spend { border-color: rgba(var(--accent-danger-rgb), 0.35); }
.coin-toast-spend .coin-toast-amount { color: var(--accent-danger); }
.coin-toast-credit { border-color: rgba(var(--accent-success-rgb), 0.4); }
.coin-toast-credit .coin-toast-amount { color: var(--accent-success); }
.coin-toast-warning {
  border-color: rgba(var(--accent-gold-rgb), 0.45);
  background: linear-gradient(135deg, rgba(var(--accent-gold-rgb), 0.12), var(--bg-elevated));
}

/* Efectos animados del nick (2026-07-21) — todos usan currentColor (el
   mismo color que ya eligió el usuario), nunca un color aparte. 'neon' y
   'sway' animan el <span> completo; 'wave'/'run' animan letra por letra
   (cada letra es su propio <span> con animation-delay creciente, armado en
   avatar.js) para que se vea la ola/carrera en cascada. */
.decorated-name.name-effect-neon {
  font-weight: 700;
  animation: name-neon-pulse 1.8s ease-in-out infinite;
}

@keyframes name-neon-pulse {
  0%, 100% {
    text-shadow: 0 0 4px currentColor, 0 0 8px currentColor;
  }
  50% {
    text-shadow: 0 0 8px currentColor, 0 0 18px currentColor, 0 0 28px currentColor;
  }
}

.decorated-name.name-effect-sway {
  display: inline-block;
  transform-origin: center bottom;
  animation: name-effect-sway-kf 1.1s ease-in-out infinite;
}

@keyframes name-effect-sway-kf {
  0%, 100% { transform: translateX(-3px) rotate(-3deg); }
  50% { transform: translateX(3px) rotate(3deg); }
}

.decorated-name.name-effect-wave span {
  display: inline-block;
  animation: name-effect-wave-kf 1s ease-in-out infinite;
}

@keyframes name-effect-wave-kf {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(-5px); }
}

.decorated-name.name-effect-run span {
  display: inline-block;
  animation: name-effect-run-kf 0.5s ease-in-out infinite;
}

@keyframes name-effect-run-kf {
  0%, 100% { transform: translateY(0) rotate(0deg); }
  25% { transform: translateY(-3px) rotate(-6deg); }
  75% { transform: translateY(2px) rotate(6deg); }
}

/* 'mariposa' (2026-07-25, pedido explícito, imagen propia lamari.png) — a
   diferencia de los otros 4 efectos (que animan el texto), este agrega una
   imagen real revoloteando arriba del nick. El wrap es el ancla de
   posicionamiento; el nombre en sí no se mueve. */
.name-mariposa-wrap {
  position: relative;
  display: inline-block;
}
.name-mariposa-icon {
  position: absolute;
  /* 2026-07-25, pedido explícito: "se mira muy chido, pero debe verse más
     grande" — subida de 18px a 30px. */
  width: 30px;
  height: 30px;
  top: -22px;
  left: 50%;
  pointer-events: none;
  animation: name-mariposa-fly 3.2s ease-in-out infinite;
}
@keyframes name-mariposa-fly {
  0%   { transform: translate(-90%, 0) rotate(-6deg); }
  25%  { transform: translate(-40%, -6px) rotate(8deg); }
  50%  { transform: translate(10%, 0) rotate(-4deg); }
  75%  { transform: translate(-40%, -4px) rotate(6deg); }
  100% { transform: translate(-90%, 0) rotate(-6deg); }
}
@media (prefers-reduced-motion: reduce) {
  .name-mariposa-icon { animation: none; }
}

/* Fondito de color del nombre, tipo "pill" (2026-07-21) — combinable con
   cualquier name-effect-* al mismo tiempo (son clases distintas, no se
   pisan). display:inline-block ya lo ponen wave/run/sway si están activos
   a la vez; si el fondito va solo (sin efecto), esta regla se lo da. */
.decorated-name.name-has-bg {
  display: inline-block;
  padding: 0.05rem 0.55rem;
  border-radius: 999px;
  font-weight: 700;
}

.profile-effect-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(80px, 1fr));
  gap: 0.4rem;
  margin-bottom: 1rem;
}

.effect-option {
  aspect-ratio: unset;
  padding: 0.5rem;
  font-size: 0.8rem;
  font-weight: 600;
}

.effect-option.selected {
  background: var(--accent-purple-soft);
  border-color: var(--accent-select);
  color: var(--accent-purple);
}

/* Roster (quién está en la sala) */
.roster-section {
  margin-top: 1.2rem;
  padding-top: 1rem;
  border-top: 1px solid var(--border-color);
}

/* Tarjeta "Hazte Premium" (2026-07-24) — visual, apunta al diamante VIP
   que ya existe en el perfil; no es un pago nuevo. */
.vip-sidebar-card {
  margin-top: 1.2rem;
  padding: 1rem;
  border-radius: var(--radius-md);
  background: linear-gradient(160deg, var(--accent-violet-soft), var(--accent-purple-soft));
  border: 1px solid rgba(var(--accent-violet-rgb), 0.35);
  text-align: center;
}
.vip-sidebar-icon {
  color: var(--accent-violet);
  margin-bottom: 0.4rem;
}
.vip-sidebar-title {
  margin: 0 0 0.25rem;
  font-weight: 700;
  font-size: 0.92rem;
  color: var(--text-primary);
}
.vip-sidebar-hint {
  margin: 0 0 0.8rem;
  font-size: 0.76rem;
  color: var(--text-secondary);
  line-height: 1.4;
}
.vip-sidebar-btn {
  width: 100%;
  border: none;
  border-radius: var(--radius-sm);
  padding: 0.55rem 0.8rem;
  background: linear-gradient(135deg, var(--accent-violet), var(--accent-purple));
  color: #fff;
  font-weight: 600;
  font-size: 0.82rem;
  cursor: pointer;
  transition: filter 0.15s ease;
}
.vip-sidebar-btn:hover { filter: brightness(1.1); }

/* Tienda de efectos/marcos (2026-07-24) */
/* Bug real reportado (2026-07-26, captura "malboton.jpeg"): quedaba pegado
   a "General" — este botón solo tenía margin-top (pensado para cuando era
   el ÚLTIMO elemento del panel, después de la tarjeta VIP); ahora que está
   ANTES de .room-list (que a su vez tiene margin:0) hace falta también
   margin-bottom para separarlo de la lista de salas. */
.store-sidebar-btn {
  margin-top: 0.7rem;
  margin-bottom: 0.7rem;
  width: 100%;
  border: 1px solid var(--border-color);
  border-radius: var(--radius-sm);
  padding: 0.55rem 0.8rem;
  background: var(--bg-surface-2);
  color: var(--text-primary);
  font-weight: 600;
  font-size: 0.82rem;
  cursor: pointer;
  transition: background 0.15s ease, border-color 0.15s ease;
}
.store-sidebar-btn:hover { background: var(--bg-elevated); border-color: var(--accent-select); }

/* Rediseño de scroll/estructura de la Tienda (2026-07-26, pedido explícito:
   "el sistema de scroll todavía se ve como un componente nativo del
   navegador"). Diagnóstico ANTES de tocar nada (pedido explícito: "no
   soluciones el problema simplemente ocultando todas las barras sin
   corregir la estructura"):
   1. El scrollbar grueso blanco era el nativo de `.modal` (regla base,
      más abajo en este archivo: max-height:85dvh + overflow-y:auto) —
      TODO el modal (header + tabs + grilla) scrolleaba junto como un solo
      bloque, por eso el header y las categorías no quedaban fijos.
   2. El modal se veía angosto en escritorio porque `.store-modal{max-
      width:640px}` perdía contra la regla base `.modal{max-width:380px}`
      — misma especificidad (una clase), y la base está más abajo en el
      archivo, así que ganaba por orden de cascada. Se soluciona escopeando
      con el ID del backdrop (`#store-modal-backdrop .modal`), que tiene
      más especificidad que `.modal` sola sin importar el orden.
   3. La barra horizontal nativa era `.store-tabs{overflow-x:auto}` sin
      ningún tratamiento de scrollbar oculto.
   Fix real (no solo ocultar barras): `.modal.store-modal` pasa a
   flex-column con `overflow:hidden` (ya NO scrollea el modal entero);
   header y `.store-tabs` quedan como hijos flex normales (fijos, nunca
   scrollean); SOLO `.modal-body` (que contiene el hint + la grilla) recibe
   `flex:1; min-height:0; overflow-y:auto` — un único scroll vertical
   interno real, no una barra oculta tapando el problema. */
#store-modal-backdrop .modal.store-modal {
  width: min(920px, calc(100vw - 40px));
  height: min(82dvh, 760px);
  max-width: none;
  max-height: none;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  background: #0d1b31;
  border: 1px solid rgba(137, 164, 220, 0.16);
  border-radius: 22px;
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.4);
}
#store-modal-backdrop .modal-header {
  flex-shrink: 0;
}
#store-modal-backdrop .modal-body {
  flex: 1;
  min-height: 0;
  min-width: 0;
  overflow-y: auto;
  overflow-x: hidden;
  overscroll-behavior: contain;
  /* Scrollbar "premium" (pedido explícito) — track casi transparente, thumb
     azul con degradado sutil, sin flechas, con espacio reservado
     (scrollbar-gutter) para que la grilla no salte de ancho cuando aparece. */
  scrollbar-gutter: stable;
  scrollbar-width: thin;
  scrollbar-color: rgba(76, 126, 255, 0.55) rgba(255, 255, 255, 0.04);
}
#store-modal-backdrop .modal-body::-webkit-scrollbar {
  width: 6px;
}
#store-modal-backdrop .modal-body::-webkit-scrollbar-track {
  background: rgba(255, 255, 255, 0.03);
}
#store-modal-backdrop .modal-body::-webkit-scrollbar-thumb {
  background: linear-gradient(180deg, rgba(76, 126, 255, 0.75), rgba(47, 137, 255, 0.55));
  border-radius: 999px;
  border: 2px solid transparent;
  background-clip: padding-box;
}
#store-modal-backdrop .modal-body::-webkit-scrollbar-thumb:hover {
  background: linear-gradient(180deg, rgba(96, 146, 255, 0.9), rgba(67, 157, 255, 0.7));
  background-clip: padding-box;
}
#store-modal-backdrop .modal-body::-webkit-scrollbar-button {
  display: none;
  height: 0;
  width: 0;
}

/* Categorías: fijas (no scrollean con la grilla), scroll horizontal propio
   SOLO cuando no entran todas — sin barra nativa visible, con scroll-snap y
   un degradado en el borde derecho como pista visual de que hay más. */
/* Envoltorio no-scrolleable (2026-07-26) — necesario para que el degradado
   de "hay más categorías" quede FIJO en el borde derecho mientras
   .store-tabs (el que de verdad scrollea) se desplaza por debajo; puesto
   directamente en .store-tabs con position:absolute se habría desplazado
   junto con el contenido en vez de quedar pineado. */
.store-tabs-wrap {
  position: relative;
  flex-shrink: 0;
}
.store-tabs {
  flex-shrink: 0;
  display: flex;
  gap: 0.4rem;
  padding: 0 1.2rem;
  border-bottom: 1px solid var(--border-color);
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x proximity;
  scrollbar-width: none;
  -ms-overflow-style: none;
  transition: box-shadow 0.15s ease;
}
.store-tabs::-webkit-scrollbar { display: none; }
/* Sombra sutil bajo las categorías cuando el contenido de abajo se
   desplazó (pedido explícito) — la clase la agrega store.js al detectar
   scrollTop > 0 en .modal-body, puramente visual, no toca ninguna lógica. */
.store-tabs.store-tabs-shadow {
  box-shadow: 0 6px 12px -8px rgba(0, 0, 0, 0.55);
}
/* Degradado a la derecha, encima de las tabs, insinuando que hay más
   categorías fuera de la vista — pseudo-elemento sobre el envoltorio fijo
   (.store-tabs-wrap), así queda pineado en el borde en vez de desplazarse
   junto con .store-tabs al scrollear. */
.store-tabs-wrap::after {
  content: '';
  position: absolute;
  top: 0;
  right: 0;
  bottom: 1px;
  width: 28px;
  background: linear-gradient(90deg, transparent, #0d1b31);
  pointer-events: none;
}
/* Ícono junto al título "Tienda" (2026-07-26) — el h3 base no es flex, se
   escopea acá para no afectar el resto de modales de la app. */
#store-modal-backdrop .modal-header h3 {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
}
.store-tab-btn {
  flex-shrink: 0;
  scroll-snap-align: start;
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  background: none;
  border: none;
  border-bottom: 2px solid transparent;
  padding: 0.7rem 0.5rem;
  color: var(--text-secondary);
  font-weight: 600;
  font-size: 0.82rem;
  cursor: pointer;
  white-space: nowrap;
}
.store-tab-btn svg { flex-shrink: 0; }
.store-tab-btn:hover { color: var(--text-primary); }
.store-tab-btn.active { color: var(--accent-select); border-bottom-color: var(--accent-select); }

.store-hint {
  font-size: 0.78rem;
  color: var(--text-secondary);
  margin: 0 0 0.8rem;
}

/* Grilla de productos (pedido explícito): 2 columnas en móvil/tablet, 3 en
   escritorio grande (media query en responsive.css) — mismo ancho de
   tarjeta dentro de cada fila, a diferencia del auto-fill de antes que
   podía dejar columnas de tamaños distintos. */
.store-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 14px;
  min-width: 0;
}
.store-item-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.35rem;
  padding: 0.6rem;
  border-radius: var(--radius-md);
  background: var(--bg-surface-2);
  border: 1px solid var(--border-color);
  text-align: center;
}
.store-item-card img {
  width: 64px;
  height: 64px;
  object-fit: contain;
}
.store-item-name {
  font-size: 0.76rem;
  font-weight: 600;
  color: var(--text-primary);
  line-height: 1.2;
}
.store-item-tier {
  font-size: 0.66rem;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  padding: 0.1rem 0.4rem;
  border-radius: 999px;
}
.store-item-tier.tier-basico { background: var(--bg-elevated); color: var(--text-secondary); }
.store-item-tier.tier-intermedio { background: rgba(var(--accent-cyan-rgb), 0.18); color: var(--accent-cyan); }
.store-item-tier.tier-alto { background: rgba(var(--accent-violet-rgb), 0.2); color: var(--accent-violet); }
.store-item-tier.tier-legendario { background: linear-gradient(135deg, var(--accent-violet), var(--accent-purple)); color: #fff; }
.store-item-price {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  font-size: 0.78rem;
  font-weight: 700;
  color: var(--text-primary);
}
.store-item-price svg { color: var(--accent-gold); flex-shrink: 0; }
.store-item-btn {
  width: 100%;
  border: none;
  border-radius: var(--radius-sm);
  padding: 0.4rem 0.5rem;
  background: var(--accent-select);
  color: #fff;
  font-weight: 600;
  font-size: 0.76rem;
  cursor: pointer;
}
.store-item-btn:hover { filter: brightness(1.1); }
.store-item-btn.equipped { background: var(--accent-success); }
.store-item-btn.unequip-btn { background: var(--bg-elevated); color: var(--text-primary); border: 1px solid var(--border-color); }
.store-item-expiry {
  font-size: 0.66rem;
  color: var(--text-secondary);
}
.store-item-expiry.expiring-soon { color: var(--accent-danger); font-weight: 600; }
.store-empty {
  grid-column: 1 / -1;
  text-align: center;
  color: var(--text-secondary);
  font-size: 0.82rem;
  padding: 1.5rem 0;
}
.roster-heading {
  margin: 0 0 0.6rem;
  font-size: 0.85rem;
  color: var(--text-secondary);
}
.roster-group-label {
  font-size: 0.7rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--text-muted);
  margin: 0.7rem 0 0.35rem;
}
.roster-group-label.owner { color: var(--accent-gold); }
.roster-row {
  display: flex;
  align-items: center;
  gap: 0.65rem;
  padding: 0.45rem 0.2rem;
}
.roster-row-text {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  line-height: 1.25;
}
.roster-kick-btn {
  flex-shrink: 0;
  padding: 0.2rem;
  font-size: 0.75rem;
  opacity: 0.6;
}
.roster-kick-btn:hover {
  opacity: 1;
}
.roster-row-name {
  font-size: 0.85rem;
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.roster-row-status {
  color: var(--text-muted);
  font-size: 0.72rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Modal de perfil */
.modal-backdrop {
  position: fixed;
  inset: 0;
  /* Insurance explícita además de position:fixed+inset:0: en iOS Safari
     viejo, un elemento fixed a veces NO se achica cuando aparece el
     teclado (se queda del tamaño de antes), tapando el campo/botón. min-
     height:100dvh sí seguía el viewport visual real. 100vh como fallback
     en navegadores sin soporte de dvh. */
  min-height: 100vh;
  min-height: 100dvh;
  background: rgba(20, 30, 50, 0.45);
  /* Separación visual real (2026-07-26, pedido explícito) — el z-index ya
     hacía que el fondo (banner de registro, topbar, salas, chat, panel de
     regalos) quedara detrás de forma funcional (confirmado con CDP), pero
     sin desenfoque se seguía leyendo demasiado nítido/competitivo con el
     modal. El blur es lo que realmente resuelve la percepción de "está
     encima" — moderado (4px) para no verse pesado en hardware débil. NUNCA
     va sobre .modal en sí (ver comentario de abajo, "no oscurecer el modal"). */
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: var(--z-modal-backdrop);
  /* max() con env(safe-area-inset-*): en un iPhone con notch/isla dinámica
     en modo PWA standalone, garantiza que el modal nunca quede recortado
     por debajo de esas zonas; en cualquier otro dispositivo env() da 0 y
     se queda igual que antes (1rem). */
  padding: max(1rem, env(safe-area-inset-top)) max(1rem, env(safe-area-inset-right)) max(1rem, env(safe-area-inset-bottom)) max(1rem, env(safe-area-inset-left));
}
.modal {
  background: var(--bg-surface);
  border-radius: var(--radius-lg);
  width: 100%;
  /* min-width:0 — bug real encontrado y corregido (2026-07-24): .modal es
     hijo de un flex row (.modal-backdrop) y por default un flex item tiene
     min-width:auto, o sea que el navegador nunca lo encoge por debajo del
     ancho mínimo de su contenido (ej. la fila de "Iniciar sesión"/"Crear
     una cuenta"). Eso hacía que la tarjeta se desbordara del viewport en
     cualquier pantalla angosta en vez de respetar max-width — confirmado
     con capturas reales a 320/390px antes del fix. */
  min-width: 0;
  max-width: 380px;
  max-height: 85vh;
  max-height: 85dvh;
  overflow-y: auto;
  box-shadow: 0 20px 50px rgba(0, 0, 0, 0.25);
  /* Scrollbar premium (2026-07-26, pedido explícito — captura "scro.png":
     "el scroll ahí no es premium, debería ser igual al que ya dejaste en
     las otras partes... solo es en pc"). Puesto en la regla BASE de .modal
     para que alcance a todos los modales de golpe (perfil, acciones,
     privados, amigos, etc.) en vez de escoparlo modal por modal — mismos
     valores que .messages/.gift-grid-scroll. En móvil los navegadores ya
     muestran scrollbar superpuesto/fino por su cuenta, por eso el usuario
     solo lo notaba en escritorio. */
  scrollbar-width: thin;
  scrollbar-color: rgba(76, 126, 255, 0.72) rgba(7, 19, 38, 0.12);
}
.modal::-webkit-scrollbar { width: 6px; }
.modal::-webkit-scrollbar-track { background: rgba(7, 19, 38, 0.12); border-radius: 999px; }
.modal::-webkit-scrollbar-thumb {
  background: rgba(76, 126, 255, 0.65);
  border-radius: 999px;
  border: 1px solid rgba(7, 19, 38, 0.55);
}
.modal::-webkit-scrollbar-thumb:hover { background: rgba(76, 126, 255, 0.9); }
/* Tarjeta de entrada — escopeada, no toca el resto de modales de la app
   (perfil, mensajes privados, wall, etc.). Radio/ancho/fondo propios según
   la integración visual pedida: fondo de tarjeta #162542 (bg-surface-2, el
   token correcto de "tarjetas y modales" de Aurora Premium — .modal base
   usa bg-surface por herencia histórica, no se toca ahí para no afectar
   los demás modales), borde discreto, radio 20px, ancho 420-460px. */
.entry-modal-backdrop .modal {
  background: var(--bg-surface-2);
  border: 1px solid var(--border-color);
  border-radius: 20px;
  max-width: 440px;
  /* Bug real corregido (2026-07-24): la tarjeta de entrada heredaba el
     max-height:85vh/85dvh + overflow-y:auto de .modal base (pensado para
     modales chicos como "perfil"/"registro") — con Google + "olvidaste tu
     contraseña" sumados, la tarjeta ya no entra en ventanas bajas y
     mostraba scrollbar PROPIA en vez de dejar crecer la página. Achica
     max-height a "sin límite" para esta tarjeta específica (no afecta los
     demás modales, ver comentario de arriba) — quien sí se encarga de
     scrollear si hace falta es .entry-modal-backdrop (regla de abajo). */
  max-height: none;
  overflow-y: visible;
}
/* Compactar un poco más la parte superior (ajuste fino 2026-07-24) — el
   cuerpo (campo de nombre) queda pegado más cerca del header ahora que
   ambos perdieron aire de sobra. */
.entry-modal-backdrop .modal-body {
  padding-top: 0.5rem;
}
.modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 1rem 1.2rem;
  border-bottom: 1px solid var(--border-color);
}
.modal-header h3 { margin: 0; font-size: 1.05rem; color: var(--text-primary); }
.modal-body { padding: 1rem 1.2rem; }
.modal-footer {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  flex-wrap: wrap;
  padding: 1rem 1.2rem;
  border-top: 1px solid var(--border-color);
}
/* Mismo degradado violeta→morado de "Hazte Premium" (2026-07-26, pedido
   explícito) — Cerrar sesión queda aparte, en rojo, porque es destructiva. */
.profile-footer-premium-btn {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  background: linear-gradient(135deg, var(--accent-violet), var(--accent-purple));
  border-color: transparent;
  color: #fff;
}
.profile-footer-premium-btn:hover { filter: brightness(1.1); background: linear-gradient(135deg, var(--accent-violet), var(--accent-purple)); }
.modal-label {
  display: block;
  font-size: 0.8rem;
  font-weight: 700;
  color: var(--text-secondary);
  margin: 0.9rem 0 0.5rem;
}
.modal-label:first-child { margin-top: 0; }

/* Ícono discreto dentro del campo de nombre (ajuste fino 2026-07-24) — solo
   posición vía CSS, el <input> real no cambia de id/comportamiento. */
.entry-input-wrap {
  position: relative;
}
.entry-input-wrap .entry-input-icon {
  position: absolute;
  left: 0.8rem;
  top: 50%;
  transform: translateY(-50%);
  color: var(--accent-cyan);
  pointer-events: none;
}
#profile-status-input {
  width: 100%;
  padding: 0.55rem 0.7rem;
  border: 1px solid var(--border-color);
  border-radius: var(--radius-sm);
  font-family: inherit;
  font-size: 0.9rem;
}

#entry-name-input {
  width: 100%;
  padding: 0.6rem 0.75rem;
  /* Bug real corregido (2026-07-24): esta regla por ID ya existía de antes
     y le ganaba en especificidad a ".entry-input-wrap input" (una clase
     nunca le gana a un ID) — el padding-left del ícono nunca se aplicaba
     de verdad, y el texto se dibujaba encima del ícono. El fix va acá,
     donde realmente se resuelve la cascada. */
  padding-left: 2.6rem;
  border: 1px solid var(--border-color);
  border-radius: var(--radius-sm);
  font-family: inherit;
  font-size: 0.95rem;
  box-sizing: border-box;
}

.entry-modal-backdrop {
  z-index: var(--z-modal-backdrop-elevated);
  /* Luz radial azul+violeta muy sutil detrás de la tarjeta (ajuste fino
     2026-07-24) — reemplaza el overlay plano genérico de .modal-backdrop
     porque esta pantalla es la primera vista, no un modal "sobre" otro
     contenido. En móvil se aplana a fondo sólido (ver responsive.css) por
     rendimiento, igual criterio que ya se usa en el fondo de <body>. */
  background:
    radial-gradient(ellipse 70% 45% at 50% 42%, rgba(var(--accent-purple-rgb), 0.16), transparent 70%),
    radial-gradient(ellipse 60% 45% at 50% 58%, rgba(var(--accent-violet-rgb), 0.12), transparent 70%),
    var(--bg-page);
  /* Bug real corregido (2026-07-24) — "Iniciar sesión" (y ahora también
     Google + "olvidaste tu contraseña") hacen que la tarjeta pueda superar
     la altura de ventanas bajas. En vez de una scrollbar DENTRO de la
     tarjeta (fea, recorta el borde redondeado), se deja scrollear este
     fondo completo — se ve como "se desplaza la página", tal como se pidió.
     flex-direction:column + los dos pseudo-elementos de abajo (flex:1,
     como resortes) centran la tarjeta cuando sobra espacio y la empujan
     arriba del todo (scrolleable) cuando no alcanza — evita el error
     clásico de "align-items:center recorta el borde de arriba" que tiene
     un center + overflow:auto normal. */
  flex-direction: column;
  align-items: center;
  justify-content: flex-start;
  overflow-y: auto;
}
.entry-modal-backdrop::before,
.entry-modal-backdrop::after {
  content: '';
  flex: 1 0 20px;
}

/* Destellos a los lados de la tarjeta (2026-07-24, pedido explícito) — dos
   rayas verticales con los mismos 3 colores del logo/burbujas (azul,
   turquesa, violeta), con un movimiento muy sutil, no una animación
   pesada. Solo en escritorio (min-width:700px, mismo criterio ya usado en
   el resto de esta pantalla): en móvil la tarjeta no deja espacio real a
   los lados y no vale la pena el costo. prefers-reduced-motion ya las
   congela solas (regla global en responsive.css). */
.entry-glow-streak {
  display: none;
  position: absolute;
  top: 50%;
  width: 3px;
  height: 55%;
  border-radius: 999px;
  pointer-events: none;
  background: linear-gradient(
    180deg,
    transparent 0%,
    rgba(var(--accent-purple-rgb), 0.55) 25%,
    rgba(var(--accent-cyan-rgb), 0.65) 50%,
    rgba(var(--accent-violet-rgb), 0.55) 75%,
    transparent 100%
  );
  filter: blur(2px);
  animation: entry-glow-drift 7s ease-in-out infinite;
}
@media (min-width: 700px) {
  .entry-glow-streak {
    display: block;
  }
}
.entry-glow-streak-left {
  left: calc(50% - 260px);
}
.entry-glow-streak-right {
  right: calc(50% - 260px);
  animation-delay: -3.5s;
}
@keyframes entry-glow-drift {
  0%, 100% { opacity: 0.45; transform: translateY(-50%) scaleY(1); }
  50% { opacity: 0.9; transform: translateY(-53%) scaleY(1.08); }
}

/* "Crear una cuenta" desde la pantalla de entrada abre este modal (ya
   existente, sin tocar su lógica) por encima — si no, quedaría tapado
   detrás del backdrop de entrada (z-index 200 > 100 de un modal normal). */
#claim-nick-modal-backdrop {
  z-index: var(--z-modal-backdrop-top);
}

/* Botón principal de la pantalla de entrada — ancho completo, mobile-first
   (regla explícita: "el botón principal debe ocupar todo el ancho
   disponible"). Escopeado a este modal, no a .btn-primary en general, para
   no forzar ancho completo en modales con varios botones en fila. */
.entry-modal-backdrop .btn-primary {
  width: 100%;
  /* Degradado sutil reservado al botón destacado de la entrada (regla ya
     establecida en el tema Aurora Premium: "no uses este degradado en
     todos los botones") + sombra azul/violeta suave a juego. */
  background: linear-gradient(135deg, var(--accent-purple), var(--accent-violet));
  box-shadow: 0 10px 24px rgba(var(--accent-purple-rgb), 0.25), 0 10px 24px rgba(var(--accent-violet-rgb), 0.18);
}
.entry-modal-backdrop .btn-primary:hover {
  background: linear-gradient(135deg, var(--accent-purple-hover), var(--accent-violet));
  filter: brightness(1.08);
}
.entry-modal-backdrop .btn-primary:active {
  background: linear-gradient(135deg, var(--accent-purple-active), var(--accent-violet));
  filter: none;
}

.profile-color-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}
.color-swatch {
  width: 30px;
  height: 30px;
  border-radius: 50%;
  border: 2px solid transparent;
  cursor: pointer;
}
.color-swatch.selected {
  border-color: var(--text-primary);
  box-shadow: 0 0 0 2px var(--bg-surface);
}

/* Fondito de color del nombre (2026-07-21) — mismo tamaño/forma que
   .color-swatch, texto "✕" para la opción "Ninguno". */
.bg-swatch {
  width: 30px;
  height: 30px;
  border-radius: 50%;
  border: 2px solid transparent;
  cursor: pointer;
  background: var(--bg-surface-2);
  color: var(--text-secondary);
  font-size: 0.8rem;
}
.bg-swatch.selected {
  border-color: var(--text-primary);
  box-shadow: 0 0 0 2px var(--bg-surface);
}

.profile-badge-grid {
  display: grid;
  /* auto-fill/minmax en vez de 8 columnas fijas (bug real reportado en
     celulares angostos: con un modal de ~280px de ancho de contenido, 8
     columnas fijas dejaban cada celda diminuta) — así se acomoda solo según
     el ancho real disponible, igual que .emoticon-grid. */
  grid-template-columns: repeat(auto-fill, minmax(44px, 1fr));
  gap: 0.35rem;
  max-height: 160px;
  overflow-y: auto;
}
.badge-option {
  position: relative;
  background: var(--bg-surface-2);
  border: 2px solid transparent;
  border-radius: var(--radius-sm);
  padding: 0.25rem;
  display: flex;
  align-items: center;
  justify-content: center;
  aspect-ratio: 1;
  cursor: pointer;
  font-size: 0.85rem;
  color: var(--text-muted);
}
.badge-option img { width: 100%; height: 100%; object-fit: contain; }
.badge-option.selected { border-color: var(--accent-select); }

/* Aros/insignias exclusivos de Premium (2026-07-26) — bloqueados
   visualmente en vez de ocultos, para que se note que existen y tienten a
   suscribirse. */
.badge-option.locked img { opacity: 0.35; filter: grayscale(0.6); }
.locked-overlay {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.1rem;
}
.profile-locked-hint {
  margin: 0 0 0.5rem;
  font-size: 0.78rem;
  color: var(--text-muted);
  background: var(--accent-violet-soft);
  border: 1px solid rgba(var(--accent-violet-rgb), 0.3);
  border-radius: var(--radius-sm);
  padding: 0.5rem 0.7rem;
}
.profile-locked-link {
  background: none;
  border: none;
  padding: 0;
  color: var(--accent-violet);
  font-weight: 700;
  font-size: 0.78rem;
  cursor: pointer;
  text-decoration: underline;
}

/* Mensajes privados + ver perfil (2026-07-21) */
.pm-inbox-btn {
  position: relative;
}
.pm-badge {
  position: absolute;
  top: -4px;
  right: -4px;
  background: var(--accent-danger);
  color: #fff;
  font-size: 0.65rem;
  font-weight: 700;
  border-radius: 999px;
  padding: 0.05rem 0.35rem;
  min-width: 16px;
  line-height: 1.3;
}

/* Saldo de monedas en la barra superior (2026-07-24) — pastilla dorada,
   mismo estilo que la referencia visual. El número real y la lógica de
   compra llegan en la Fase B/C (por ahora el elemento existe hidden en el
   HTML hasta que haya un saldo real que mostrar). */
.coin-balance-pill {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  background: var(--bg-surface-2);
  border: 1px solid var(--border-color);
  border-radius: 999px;
  padding: 0.35rem 0.5rem 0.35rem 0.7rem;
  color: var(--accent-gold);
  font-weight: 700;
  font-size: 0.85rem;
}
.coin-balance-pill svg { color: var(--accent-gold); flex-shrink: 0; }
#coin-balance-amount { color: var(--text-primary); font-variant-numeric: tabular-nums; }
.coin-buy-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  border: none;
  background: var(--accent-purple);
  color: #fff;
  font-size: 1rem;
  font-weight: 700;
  line-height: 1;
  cursor: pointer;
  transition: background 0.18s ease;
}
.coin-buy-btn:hover { background: var(--accent-purple-hover); }

.roster-row-clickable {
  cursor: pointer;
}
.roster-row-clickable:hover {
  background: var(--bg-elevated);
  border-radius: var(--radius-sm);
}

/* Menú de acciones sobre un usuario, rediseñado como bottom sheet "Midnight
   Royal" (2026-07-26, prompt qwaq.txt, mockup eldemoapp.png) — antes era
   una tarjeta centrada con 4 botones apilados de ancho completo y solo un
   título de texto plano. Ahora: hoja que sube desde abajo con indicador de
   arrastre, encabezado con avatar+insignias reales (profile:view, el mismo
   evento que ya usaba "Ver perfil"), grilla 2x2 y un botón ancho de amistad
   debajo (nuevo, ver renderFriendButton en privateMessages.js). */
.user-action-modal {
  max-width: 360px;
  width: calc(100% - 24px);
  border-radius: var(--radius-xl);
  align-self: flex-end;
  margin-bottom: max(12px, env(safe-area-inset-bottom));
}
/* Bug real reportado (2026-07-26, dos veces — la primera vez solo se
   corrigió en escritorio "por las dudas" de no tocar el mockup aprobado,
   pero el pedido original YA decía explícitamente "tanto en pc como en
   móviles debería estar centrada"). Centrado en TODOS los tamaños ahora,
   sin importar el ancho — ya no queda ningún bottom sheet para este menú. */
.user-action-modal {
  align-self: center;
  margin-bottom: 0;
}
.user-action-drag-handle {
  display: none;
}
.user-action-drag-handle {
  width: 36px;
  height: 4px;
  border-radius: 999px;
  background: var(--border-soft);
  margin: 10px auto 0;
}
.user-action-header {
  gap: 0.7rem;
}
.user-action-avatar {
  flex-shrink: 0;
}
.user-action-avatar .avatar,
.user-action-avatar .avatar-frame-wrap {
  width: 44px;
  height: 44px;
  min-width: 44px;
}
.user-action-header-info {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
}
.user-action-header-info h3 {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.user-action-online {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  margin: 0.1rem 0 0;
  font-size: 0.78rem;
  color: var(--accent-success);
}
.online-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--accent-success);
  box-shadow: 0 0 0 2px rgba(var(--accent-success-rgb), 0.25);
  flex-shrink: 0;
}
.user-action-badges {
  display: flex;
  align-items: center;
  gap: 0.3rem;
  flex-wrap: wrap;
}
.user-action-badges img { height: 16px; width: auto; }
.user-action-buttons {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0.6rem;
}
.user-action-grid-btn {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.4rem;
  min-height: 78px;
  padding: 0.75rem 0.5rem;
  background: var(--bg-elevated);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-md);
  color: var(--text-primary);
  font-size: 0.82rem;
  font-weight: 600;
  transition: transform 0.18s ease, box-shadow 0.18s ease, border-color 0.18s ease, background 0.18s ease;
}
.user-action-grid-btn svg { flex-shrink: 0; }
.user-action-grid-btn:hover {
  border-color: rgba(var(--accent-violet-rgb), 0.5);
  box-shadow: 0 0 0 3px rgba(var(--accent-violet-rgb), 0.14);
  transform: translateY(-2px);
}
.user-action-grid-btn:active { transform: translateY(0) scale(0.98); }
.user-action-grid-btn-primary {
  background: linear-gradient(145deg, var(--accent-purple), var(--accent-purple-active));
  border-color: transparent;
  color: #fff;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.12), 0 4px 14px rgba(var(--accent-purple-rgb), 0.35);
}
.user-action-grid-btn-primary:hover {
  background: linear-gradient(145deg, var(--accent-purple-hover), var(--accent-purple));
  box-shadow: 0 0 0 3px rgba(var(--accent-purple-rgb), 0.18), 0 6px 18px rgba(var(--accent-purple-rgb), 0.4);
}
/* Bug real reportado (2026-07-26): "Agregar amigo" quedaba pegado/encimado
   a la línea separadora de arriba — padding-top:0 no dejaba nada de aire
   entre el border-top de .modal-footer (heredado de la regla base) y el
   botón. */
.user-action-footer {
  padding-top: 0.8rem;
}
.user-action-friend-btn {
  width: 100%;
  justify-content: center;
  min-height: 48px;
  background: var(--bg-elevated);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-md);
  color: var(--text-primary);
  font-weight: 600;
  display: flex;
  align-items: center;
  gap: 0.4rem;
}

/* Rediseño "Midnight Royal" del modal de perfil (2026-07-26, prompt
   qwaq.txt, mockup eldemoapp.png): portada decorativa detrás del avatar,
   avatar superpuesto entre la portada y el cuerpo, nombre en su propia
   fila (renderNameOnlyHTML) e insignias en una fila aparte debajo
   (renderProfileBadgesRowHTML) — antes iba todo concatenado en una sola
   línea desordenada. */
.view-profile-modal {
  border-radius: var(--radius-xl);
  overflow: hidden;
}
.view-profile-modal .modal-header {
  border-bottom: none;
  position: relative;
  z-index: 1;
  /* 20-24px (2026-07-26, pedido explícito) — el padding base de
     .modal-header (1.2rem ≈ 19.2px) dejaba el título "Perfil" casi pegado
     al borde. Escopeado solo a este modal, no toca el resto. */
  padding: 20px 22px;
}
/* Ancho de escritorio (2026-07-26, pedido explícito: "aumentar únicamente
   el ancho máximo a aproximadamente 420-440px... no cambiar el diseño
   móvil") — mismo breakpoint de 700px ya usado en este archivo para otros
   ajustes solo-desktop. Todo lo demás del modal sigue igual. */
@media (min-width: 700px) {
  .view-profile-modal {
    max-width: 430px;
  }
}
.view-profile-cover {
  height: 90px;
  margin-top: -1px;
  background:
    radial-gradient(ellipse 70% 100% at 50% 0%, rgba(var(--accent-violet-rgb), 0.28), transparent 70%),
    radial-gradient(ellipse 60% 80% at 50% 10%, rgba(var(--accent-purple-rgb), 0.22), transparent 70%),
    var(--bg-surface-2);
}
.view-profile-body {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 0.35rem;
  padding: 0 1rem 1rem;
}
.view-profile-avatar {
  margin-top: -54px;
}
.view-profile-avatar .avatar,
.view-profile-avatar .avatar-frame-wrap {
  width: 88px;
  height: 88px;
  min-width: 88px;
}
.view-profile-name {
  font-size: 1.2rem;
  font-weight: 700;
  margin-top: 0.3rem;
}
.view-profile-badges {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.35rem;
  flex-wrap: wrap;
  min-height: 20px;
}
.view-profile-badges img { height: 18px; width: auto; }
.view-profile-quote-card {
  width: 100%;
  display: flex;
  align-items: flex-start;
  gap: 0.5rem;
  margin-top: 0.4rem;
  padding: 0.65rem 0.8rem;
  background: var(--bg-surface-2);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-md);
  text-align: left;
}
.view-profile-quote-card svg {
  flex-shrink: 0;
  color: var(--accent-cyan);
  margin-top: 0.1rem;
}
.view-profile-status {
  color: var(--text-secondary);
  font-size: 0.88rem;
  margin: 0;
  /* Límite de 3 líneas (2026-07-26, pedido explícito: "evitar que un
     versículo largo expanda excesivamente el modal... opción de expansión
     futura") — line-clamp corta con "…", sin botón de "ver más" todavía
     (eso queda para una fase futura, no pedida ahora). */
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.view-profile-stats-row {
  width: 100%;
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  gap: 0.5rem;
  margin-top: 0.5rem;
}
.view-profile-stat {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.15rem;
  padding: 0.55rem 0.3rem;
  background: var(--bg-surface-2);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-md);
}
.view-profile-stat svg { color: var(--accent-purple); }
.view-profile-stat span {
  font-weight: 700;
  font-size: 0.95rem;
}
.view-profile-stat small {
  color: var(--text-muted);
  font-size: 0.72rem;
}
.view-profile-meta {
  color: var(--text-muted);
  font-size: 0.8rem;
}
/* Solo la ve el dueño (2026-07-23, permiso endurecido a is_owner el
   2026-07-26) — el servidor ya filtra esto, ver privateMessageHandlers.js
   profile:view. Sección chica y discreta a propósito (pedido explícito de
   ultimo.txt, afinado el mismo día: borde fino sólido en vez de punteado,
   fondo apenas diferente al del modal — no debe competir visualmente con
   el resto del perfil). */
.view-profile-admin-section {
  border: 1px solid var(--border-color);
  border-radius: var(--radius-sm);
  background: var(--bg-surface-2);
  padding: 0.5rem 0.7rem;
  margin: 0.4rem 0;
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
}
.view-profile-admin-title {
  font-size: 0.68rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-muted);
}
.view-profile-ip {
  font-family: monospace;
  /* Rojo intenso reservado para cuando de verdad es una alerta (baneado,
     ver .view-profile-ip-alert / privateMessages.js) — la IP sola es solo
     informativa. */
  color: var(--text-secondary);
}
.view-profile-ip.view-profile-ip-alert {
  color: var(--accent-danger);
}

/* Botones "premium" del pie del modal "Ver perfil" (2026-07-25, pedido
   explícito: "Enviar mensaje privado"/"Banear" se veían planos) — escopeado
   a #view-profile-backdrop a propósito para no tocar .btn-primary/.icon-btn
   .danger genéricos usados en el resto de la app (formularios, cerrar
   sesión, bajar del escenario, etc.). */
#view-profile-backdrop .modal-footer .btn-primary {
  background: linear-gradient(145deg, var(--accent-purple), var(--accent-purple-active));
  border: 1px solid rgba(255, 255, 255, 0.08);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.12), 0 4px 14px rgba(var(--accent-purple-rgb), 0.35);
  transition: transform 0.18s ease, box-shadow 0.18s ease, background 0.18s ease;
}
#view-profile-backdrop .modal-footer .btn-primary:hover {
  background: linear-gradient(145deg, var(--accent-purple-hover), var(--accent-purple));
  box-shadow: 0 0 0 3px rgba(var(--accent-purple-rgb), 0.18), 0 6px 18px rgba(var(--accent-purple-rgb), 0.4);
  transform: translateY(-2px);
}
#view-profile-backdrop .modal-footer .btn-primary:active {
  transform: translateY(0) scale(0.98);
}
#view-profile-backdrop .modal-footer .icon-btn.danger {
  background: linear-gradient(145deg, var(--accent-danger), #d8455e);
  border: 1px solid rgba(255, 255, 255, 0.08);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.12), 0 4px 14px rgba(var(--accent-danger-rgb), 0.3);
  transition: transform 0.18s ease, box-shadow 0.18s ease, background 0.18s ease;
}
#view-profile-backdrop .modal-footer .icon-btn.danger:hover {
  box-shadow: 0 0 0 3px rgba(var(--accent-danger-rgb), 0.2), 0 6px 18px rgba(var(--accent-danger-rgb), 0.4);
  transform: translateY(-2px);
}
#view-profile-backdrop .modal-footer .icon-btn.danger:active {
  transform: translateY(0) scale(0.98);
}

/* Bug real reportado (2026-07-25, captura móvil "error-estetico-cel.jpeg"):
   la tarjeta "Ver perfil" quedaba cortada abajo en celular (el botón
   "Enviar mensaje privado"/"Banear" se veía a medias, sin scrollbar visible
   ni forma de llegar al resto). Causa: .modal hereda max-height:85dvh +
   overflow-y:auto de la regla base — en la práctica, con la barra de
   direcciones de Chrome Android visible, el `dvh` calculado al abrir el
   modal no se recalcula si la barra cambia de estado, dejando la tarjeta
   más alta que el viewport visual real SIN que el scroll interno alcance a
   compensarlo. Mismo fix ya probado en `.entry-modal-backdrop` (arriba en
   este archivo) para el mismo síntoma: en vez de una scrollbar DENTRO de
   la tarjeta (que además recorta las esquinas redondeadas), se deja
   scrollear el fondo completo — la tarjeta nunca se corta, en el peor caso
   la página se desplaza. Los pseudo-elementos flex:1 como "resortes" la
   centran cuando sobra espacio y la empujan arriba (scrolleable) cuando no
   alcanza. */
#view-profile-backdrop {
  flex-direction: column;
  align-items: center;
  justify-content: flex-start;
  overflow-y: auto;
  /* Scrollbar premium (2026-07-26, captura "scro.png"; afinado el mismo
     día: track casi transparente, 5px, sin scrollbar-gutter — ya solo
     aparece cuando hay overflow real, que es lo que se pidió). Acá quien
     scrollea de verdad es el BACKDROP, no .modal (ver comentario de
     arriba), así que el scrollbar-color puesto en la regla base de .modal
     nunca se veía acá; mismos valores, escopeados a este backdrop
     específico. */
  scrollbar-width: thin;
  scrollbar-color: rgba(76, 126, 255, 0.72) rgba(7, 19, 38, 0.03);
}
#view-profile-backdrop::-webkit-scrollbar { width: 5px; }
#view-profile-backdrop::-webkit-scrollbar-track { background: rgba(7, 19, 38, 0.03); border-radius: 999px; }
#view-profile-backdrop::-webkit-scrollbar-thumb {
  background: rgba(76, 126, 255, 0.65);
  border-radius: 999px;
  border: 1px solid rgba(7, 19, 38, 0.55);
}
#view-profile-backdrop::-webkit-scrollbar-thumb:hover { background: rgba(76, 126, 255, 0.9); }
#view-profile-backdrop::before,
#view-profile-backdrop::after {
  content: '';
  flex: 1 0 20px;
}
#view-profile-backdrop .modal {
  max-height: none;
  overflow-y: visible;
}

/* Botón de amistad del modal "Perfil" (2026-07-25) — un solo botón que
   cambia de color/texto según el estado (ver renderFriendButton en
   privateMessages.js), mismo criterio de "una clase por estado" que ya usan
   los estilos de tarjeta de perfil (profileStyle). */
.friend-action-btn.friend-btn-outgoing {
  color: var(--text-secondary);
}
.friend-action-btn.friend-btn-incoming {
  background: linear-gradient(135deg, var(--accent-violet), var(--accent-purple));
  border-color: transparent;
  color: #fff;
}
.friend-action-btn.friend-btn-incoming:hover {
  filter: brightness(1.1);
}
.friend-action-btn.friend-btn-friends {
  color: var(--accent-success);
  border-color: var(--accent-success);
}
/* Ordenar mejor el área inferior del perfil (2026-07-26, pedido explícito)
   — Mensaje privado ocupa el espacio que sobra (sigue siendo la acción
   principal, ahora sin dejar hueco vacío a la derecha cuando Agregar
   amigo/Banear están ocultos), los otros dos quedan compactos a su ancho
   natural de ícono. Mismos botones/handlers de siempre, solo layout. */
.view-profile-footer {
  justify-content: flex-start;
}
.view-profile-pm-btn {
  flex: 1;
  justify-content: center;
}

/* Rediseño "Midnight Royal" de mensajes privados (2026-07-26, prompt
   qwaq.txt, mockup eldemoapp.png) — buscador + tabs Todos/No leídos sobre
   la lista (privateMessages.js filtra 100% client-side), filas más altas
   con hora y vista previa en líneas separadas, texto de confianza al pie
   de la lista. En móvil pasa a pantalla casi completa (ver responsive.css). */
.pm-modal {
  max-width: 420px;
  height: 70vh;
  max-height: 560px;
  display: flex;
  flex-direction: column;
  border-radius: var(--radius-xl);
  overflow: hidden;
}
.pm-modal .modal-body {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  padding: 0;
}
/* .modal-header base usa justify-content:space-between, pensado para 2
   elementos (título + cerrar) — acá hay 4 (atrás, título, badge, cerrar),
   así que se ancla todo al inicio y solo "cerrar" se empuja al final. */
.pm-modal .modal-header {
  justify-content: flex-start;
  gap: 0.6rem;
}
.pm-modal .modal-header #pm-close-btn {
  margin-left: auto;
}
.pm-modal-unread-badge {
  background: var(--accent-purple);
  color: #fff;
  font-size: 0.72rem;
  font-weight: 700;
  border-radius: 999px;
  padding: 0.1rem 0.5rem;
  margin-left: 0.4rem;
}
.pm-list-toolbar {
  padding: 0.7rem 1rem 0.6rem;
  border-bottom: 1px solid var(--border-color);
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}
.pm-search-wrap {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem 0.75rem;
  background: var(--input-bg);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-md);
}
.pm-search-wrap svg { color: var(--text-muted); flex-shrink: 0; }
.pm-search-wrap input {
  flex: 1;
  min-width: 0;
  border: none;
  background: none;
  color: var(--text-primary);
  font-size: 0.88rem;
}
.pm-search-wrap input:focus { outline: none; }
.pm-tabs {
  display: flex;
  gap: 0.4rem;
}
.pm-tab-btn {
  min-height: 44px;
  padding: 0.5rem 1rem;
  border-radius: 999px;
  border: 1px solid var(--border-soft);
  background: var(--bg-surface-2);
  color: var(--text-secondary);
  font-size: 0.82rem;
  font-weight: 600;
}
.pm-tab-btn.active {
  background: var(--accent-purple);
  border-color: transparent;
  color: #fff;
}
.pm-modal-body {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
}
.pm-conversations-list {
  display: flex;
  flex-direction: column;
}
.pm-conversation-row {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  min-height: 68px;
  padding: 0.6rem 1rem;
  background: none;
  border: none;
  border-bottom: 1px solid var(--border-color);
  text-align: left;
  cursor: pointer;
  color: var(--text-primary);
}
.pm-conversation-row:hover,
.pm-conversation-row:active {
  background: var(--bg-surface-hover);
}
/* Fondo azul más claro para no-leídos (2026-07-26, pedido explícito: "un
   poco más claro") — antes 0.06 de opacidad casi no se notaba. */
.pm-conversation-row.unread {
  background: rgba(var(--accent-purple-rgb), 0.14);
}
/* Punto de "conectado" sobre el avatar de cada conversación (2026-07-26,
   pedido explícito) — mismo .online-dot que el menú de acciones, solo con
   posición absoluta para superponerlo en la esquina del avatar. */
.pm-avatar-with-dot {
  position: relative;
  display: inline-flex;
  flex-shrink: 0;
}
.pm-conversation-dot {
  position: absolute;
  bottom: 0;
  right: 0;
  box-shadow: 0 0 0 2px var(--bg-surface);
}
.pm-conversation-info {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
}
.pm-conversation-top-line,
.pm-conversation-bottom-line {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
}
.pm-conversation-name {
  font-weight: 700;
  font-size: 0.9rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.pm-conversation-time {
  flex-shrink: 0;
  color: var(--text-muted);
  font-size: 0.72rem;
}
.pm-conversation-preview {
  flex: 1;
  min-width: 0;
  color: var(--text-muted);
  font-size: 0.83rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.pm-conversation-unread {
  flex-shrink: 0;
  background: var(--accent-purple);
  color: #fff;
  font-size: 0.7rem;
  font-weight: 700;
  border-radius: 999px;
  padding: 0.1rem 0.45rem;
  min-width: 1.2rem;
  text-align: center;
}
.pm-empty {
  padding: 2.5rem 1.5rem;
  color: var(--text-muted);
  text-align: center;
  font-size: 0.85rem;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.6rem;
}
.pm-empty svg { color: var(--text-muted); opacity: 0.6; }
.pm-trust-footer {
  /* Más separación de la última conversación (pedido explícito) — antes
     0.5rem se sentía pegado al borde inferior de la lista. */
  margin: 1.2rem 0 0.8rem;
  padding: 0 1rem;
  text-align: center;
  color: var(--text-muted);
  font-size: 0.75rem;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.35rem;
}
.pm-trust-footer svg { flex-shrink: 0; opacity: 0.8; }

/* Animación de entrada corta (2026-07-26, prompt qwaq.txt: "180-220ms",
   "respetar prefers-reduced-motion") — el bottom sheet de acciones sube
   desde abajo, los otros dos aparecen con una aparición+escala sutil.
   Envuelta en la media query, así que con reduced-motion activado estos 3
   modales simplemente aparecen/desaparecen sin animar (igual que el resto
   de la app), nunca quedan invisibles por un keyframe que no llegó a correr. */
@media (prefers-reduced-motion: no-preference) {
  .user-action-modal {
    animation: user-action-sheet-in 200ms ease-out;
  }
  .view-profile-modal,
  .pm-modal {
    animation: profile-modal-pop-in 190ms ease-out;
  }
}
@keyframes user-action-sheet-in {
  from { transform: translateY(24px); opacity: 0; }
  to { transform: translateY(0); opacity: 1; }
}
@keyframes profile-modal-pop-in {
  from { transform: scale(0.96); opacity: 0; }
  to { transform: scale(1); opacity: 1; }
}

.pm-thread-view {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
}
.pm-thread-messages {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: 0.8rem 1rem;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}
.pm-thread-msg {
  max-width: 80%;
  background: var(--bg-surface-2);
  border-radius: var(--radius-md);
  padding: 0.4rem 0.65rem;
}
.pm-thread-msg.mine {
  align-self: flex-end;
  background: var(--accent-purple-soft);
}
.pm-thread-msg p {
  margin: 0.15rem 0 0;
  font-size: 0.9rem;
}
.pm-modal-footer {
  display: flex;
  gap: 0.5rem;
}
#pm-thread-input {
  flex: 1;
}

/* QuizBot (2026-07-21) — ranking de puntos y panel de configuración del
   admin. Las preguntas del bot en sí no tienen CSS propio: llegan como
   mensajes de chat normales y usan el mismo .msg de siempre. */
.quiz-leaderboard-list {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
}
.quiz-leaderboard-row {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.5rem 0.2rem;
  border-bottom: 1px solid var(--border-color);
}
.quiz-leaderboard-rank {
  font-weight: 700;
  color: var(--text-muted);
  width: 2rem;
  flex-shrink: 0;
}
.quiz-leaderboard-name {
  flex: 1;
  font-weight: 600;
  font-size: 0.9rem;
}
.quiz-leaderboard-points {
  color: var(--accent-purple);
  font-weight: 700;
  font-size: 0.85rem;
}
.quiz-leaderboard-empty {
  padding: 1.5rem 1rem;
  color: var(--text-muted);
  text-align: center;
  font-size: 0.85rem;
}
#quiz-admin-backdrop select,
#quiz-admin-backdrop input[type='number'] {
  width: 100%;
  margin-bottom: 0.8rem;
}

/* Panel de usuarios en línea (2026-07-21, otro.txt punto 2 / boomx2.png) */
/* Muro de anuncios + peticiones de oración (2026-07-24) — un solo panel con
   dos pestañas, mismo criterio flex-column de .pm-modal para que el feed
   scrollee y el compose quede siempre visible abajo. */
.wall-modal {
  max-width: 480px;
  height: 78vh;
  max-height: 640px;
  display: flex;
  flex-direction: column;
}
.wall-tabs {
  display: flex;
  border-bottom: 1px solid var(--border-color);
  flex-shrink: 0;
}
.wall-tab-btn {
  flex: 1;
  background: none;
  border: none;
  padding: 0.7rem 0.5rem;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--text-secondary);
  cursor: pointer;
  border-bottom: 2px solid transparent;
}
.wall-tab-btn.active {
  color: var(--accent-purple);
  border-bottom-color: var(--accent-purple);
}
.wall-modal .modal-body,
.wall-body {
  flex: 1;
  min-height: 0;
  padding: 0;
  overflow: hidden;
}
.wall-feed {
  height: 100%;
  overflow-y: auto;
  padding: 0.8rem 1rem;
  display: flex;
  flex-direction: column;
  gap: 0.8rem;
}
.wall-empty {
  text-align: center;
  color: var(--text-muted);
  padding: 2rem 0;
  font-size: 0.9rem;
}
.wall-post {
  background: var(--bg-elevated);
  border-radius: var(--radius-md);
  padding: 0.8rem;
}
.wall-post-header {
  display: flex;
  align-items: center;
  gap: 0.6rem;
}
.wall-post-author {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
}
.wall-post-time {
  font-size: 0.7rem;
  color: var(--text-muted);
}
.wall-post-delete-btn {
  flex-shrink: 0;
}
.wall-post-body {
  margin: 0.6rem 0 0;
  white-space: pre-wrap;
  word-break: break-word;
  font-size: 0.92rem;
}
.wall-post-image {
  max-width: 100%;
  border-radius: var(--radius-sm);
  margin-top: 0.6rem;
  display: block;
}
.wall-post-actions {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin-top: 0.6rem;
  padding-top: 0.6rem;
  border-top: 1px solid var(--border-color);
}
.wall-reaction-btn {
  background: var(--bg-surface);
  border: 1px solid var(--border-color);
  border-radius: 999px;
  padding: 0.25rem 0.6rem;
  font-size: 0.8rem;
  cursor: pointer;
  color: var(--text-secondary);
}
.wall-reaction-btn.active {
  background: var(--accent-purple-soft);
  border-color: var(--accent-purple);
  color: var(--accent-purple);
}
.wall-comment-toggle-btn {
  background: none;
  border: none;
  margin-left: auto;
  font-size: 0.8rem;
  color: var(--text-secondary);
  cursor: pointer;
}
.wall-comments {
  margin-top: 0.6rem;
  padding-top: 0.6rem;
  border-top: 1px solid var(--border-color);
}
.wall-comments-list {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  max-height: 220px;
  overflow-y: auto;
}
.wall-comments-empty {
  color: var(--text-muted);
  font-size: 0.8rem;
  margin: 0;
}
.wall-comment-row {
  display: flex;
  align-items: flex-start;
  gap: 0.5rem;
}
.wall-comment-bubble {
  flex: 1;
  min-width: 0;
  background: var(--bg-surface);
  border-radius: var(--radius-sm);
  padding: 0.4rem 0.6rem;
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
}
.wall-comment-name {
  font-size: 0.78rem;
  font-weight: 700;
}
.wall-comment-body {
  font-size: 0.85rem;
  word-break: break-word;
}
.wall-comment-compose {
  display: flex;
  gap: 0.4rem;
  margin-top: 0.5rem;
}
.wall-comment-input {
  flex: 1;
  padding: 0.4rem 0.6rem;
  border: 1px solid var(--border-color);
  border-radius: var(--radius-sm);
  font-family: inherit;
  font-size: 0.85rem;
}
.wall-compose {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  flex-shrink: 0;
}
.wall-compose-hint {
  margin: 0;
  font-size: 0.8rem;
  color: var(--text-muted);
}
.wall-compose-row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}
#wall-compose-input {
  flex: 1;
  padding: 0.55rem 0.7rem;
  border: 1px solid var(--border-color);
  border-radius: var(--radius-sm);
  font-family: inherit;
  font-size: 0.9rem;
}
.wall-compose-image-preview {
  position: relative;
  width: fit-content;
}
.wall-compose-image-preview img {
  max-height: 90px;
  border-radius: var(--radius-sm);
  display: block;
}
.wall-compose-image-preview button {
  position: absolute;
  top: -8px;
  right: -8px;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  border: none;
  background: var(--accent-danger);
  color: #fff;
  cursor: pointer;
  font-size: 0.75rem;
  line-height: 1;
}

/* Efecto grande de regalo estilo TikTok (2026-07-24, pedido explícito) —
   flota centrado arriba, sobre video/chat/karaoke, pointer-events:none para
   no bloquear clicks debajo. Varios regalos seguidos se apilan (column,
   gap) en vez de reemplazarse. */
.gift-overlay-container {
  position: fixed;
  top: 70px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 9999;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.5rem;
  pointer-events: none;
}
@keyframes gift-overlay-in {
  0% { opacity: 0; transform: scale(0.4) translateY(-30px); }
  60% { opacity: 1; transform: scale(1.15) translateY(4px); }
  100% { opacity: 1; transform: scale(1) translateY(0); }
}
.gift-overlay-item {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  background: linear-gradient(135deg, rgba(27, 44, 75, 0.92), rgba(var(--accent-purple-rgb), 0.35));
  border: 1px solid rgba(var(--accent-purple-rgb), 0.4);
  border-radius: 999px;
  padding: 0.5rem 1.2rem 0.5rem 0.5rem;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.35);
  animation: gift-overlay-in 0.5s cubic-bezier(.25, 1.5, .5, 1) both;
  transition: opacity 0.5s ease, transform 0.5s ease;
}
.gift-overlay-item.gift-overlay-leaving {
  opacity: 0;
  transform: translateY(-20px) scale(0.9);
}
.gift-overlay-img {
  width: 64px;
  height: 64px;
  object-fit: contain;
  filter: drop-shadow(0 4px 10px rgba(0, 0, 0, 0.4));
}
.gift-overlay-text {
  display: flex;
  flex-direction: column;
  color: #fff;
  font-size: 0.9rem;
  line-height: 1.3;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.6);
}
.gift-overlay-text strong {
  font-size: 1rem;
}
@media (max-width: 640px) {
  .gift-overlay-container { top: 60px; }
  .gift-overlay-img { width: 52px; height: 52px; }
  .gift-overlay-item { padding: 0.4rem 0.9rem 0.4rem 0.4rem; }
}

.online-users-modal {
  max-width: 420px;
}
.online-users-count-badge {
  background: var(--accent-purple-soft);
  color: var(--accent-purple);
  font-size: 0.8rem;
  font-weight: 700;
  border-radius: 999px;
  padding: 0.1rem 0.55rem;
  margin-left: 0.4rem;
}
.online-users-list {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  max-height: 60vh;
  overflow-y: auto;
}
.online-user-row {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  padding: 0.55rem 0.3rem;
  border-radius: var(--radius-md);
}
.online-user-row-clickable {
  cursor: pointer;
}
.online-user-row-clickable:hover {
  background: var(--bg-elevated);
}
.online-user-info {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
}
.online-user-name {
  font-weight: 600;
  font-size: 0.92rem;
  display: flex;
  align-items: center;
  gap: 0.35rem;
}
.online-user-status {
  color: var(--text-muted);
  font-size: 0.78rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.online-user-star {
  color: var(--accent-gold);
  flex-shrink: 0;
}
.online-users-empty {
  padding: 1.5rem 1rem;
  color: var(--text-muted);
  text-align: center;
  font-size: 0.85rem;
}
/* Amigos (2026-07-25) — mismo patrón visual que .online-users-modal/
   .online-user-row (fila con avatar+nombre), con una columna de acciones
   extra para las pestañas de solicitudes recibidas/enviadas. */
.friends-modal {
  max-width: 420px;
  display: flex;
  flex-direction: column;
}
.friends-tab-badge {
  background: var(--accent-danger);
  color: #fff;
  font-size: 0.7rem;
  font-weight: 700;
  border-radius: 999px;
  padding: 0.05rem 0.4rem;
  margin-left: 0.3rem;
}
.friends-panel {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  max-height: 60vh;
  overflow-y: auto;
  padding: 1rem 1.2rem;
}
.friend-row {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  padding: 0.55rem 0.3rem;
  border-radius: var(--radius-md);
}
.friend-row-clickable {
  cursor: pointer;
}
.friend-row-clickable:hover {
  background: var(--bg-elevated);
}
.friend-row-info {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
}
/* Bug real reportado (2026-07-26, captura "botonencimado.png"): con un
   nombre largo + insignia VIP, el botón "Aceptar"/"Rechazar"/"Cancelar"
   quedaba encimado sobre el texto, en pc y en móvil. Causa: .friend-row-name
   es un flex row (diamante + nombre + insignia + bandera) SIN min-width:0 —
   por default un flex item no se encoge por debajo del ancho de su
   contenido, así que en vez de achicarse se desbordaba por encima de
   .friend-row-actions (que pinta después en el DOM, por eso tapaba el
   nombre en vez de al revés). Mismo patrón de bug ya visto antes en este
   proyecto con .modal (ver comentario ahí). Fix: min-width:0 para que
   pueda encogerse, y el texto del nombre en sí (.decorated-name adentro)
   trunca con "…" en una sola línea — el diamante/insignia/bandera nunca
   se achican, solo el texto. */
.friend-row-name {
  font-weight: 600;
  font-size: 0.92rem;
  display: flex;
  align-items: center;
  gap: 0.35rem;
  min-width: 0;
}
.friend-row-name .decorated-name {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.friend-row-status {
  color: var(--text-muted);
  font-size: 0.78rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.friend-row-actions {
  display: flex;
  gap: 0.4rem;
  flex-shrink: 0;
}
.friend-row-actions .icon-btn {
  padding: 0.35rem 0.6rem;
  font-size: 0.8rem;
}
.friends-empty {
  padding: 1.5rem 1rem;
  color: var(--text-muted);
  text-align: center;
  font-size: 0.85rem;
}

.country-flag {
  width: 1.1em;
  height: 0.75em;
  object-fit: cover;
  border-radius: 1px;
  flex-shrink: 0;
  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.25);
}

/* Botón "+" con GIFs/YouTube (2026-07-21, otro.txt puntos 4-5) */
.plus-menu {
  display: flex;
  gap: 0.4rem;
  padding: 0.4rem 0;
}
.plus-menu-option {
  background: var(--bg-surface-2);
  border: 1px solid var(--border-color);
  color: var(--text-primary);
  border-radius: var(--radius-sm);
  padding: 0.45rem 0.8rem;
  font-size: 0.85rem;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
}
.plus-menu-option svg { flex-shrink: 0; color: var(--text-muted); }
.plus-menu-option:hover {
  background: var(--bg-elevated);
}

.media-search-modal {
  max-width: 420px;
}
#gif-search-input,
#youtube-search-input {
  width: 100%;
}
.media-search-empty {
  padding: 1.5rem 1rem;
  color: var(--text-muted);
  text-align: center;
  font-size: 0.85rem;
}

.gif-search-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 0.5rem;
  margin-top: 0.6rem;
  max-height: 50vh;
  overflow-y: auto;
}
.gif-result {
  background: var(--bg-surface-2);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-md);
  padding: 0;
  overflow: hidden;
  aspect-ratio: 1;
  cursor: pointer;
}
.gif-result img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.gif-result:hover {
  border-color: var(--accent-purple);
}

.youtube-search-list {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  margin-top: 0.6rem;
  max-height: 50vh;
  overflow-y: auto;
}
.youtube-result {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  background: var(--bg-surface-2);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-md);
  padding: 0.5rem;
  cursor: pointer;
  text-align: left;
}
.youtube-result:hover {
  border-color: var(--accent-purple);
}
.youtube-result img {
  width: 90px;
  height: 50px;
  object-fit: cover;
  border-radius: var(--radius-sm);
  flex-shrink: 0;
}
.youtube-result-title {
  font-size: 0.85rem;
  overflow: hidden;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}

/* GIF/video compartidos, ya dentro de un mensaje de chat */
.shared-gif {
  max-width: 220px;
  max-height: 220px;
  border-radius: var(--radius-md);
  display: block;
}

/* Foto/video subido por el botón "+" (2026-07-22) — un poco más grande que
   el GIF (son fotos reales, no un sticker chico) pero con el mismo tope de
   ancho para no romper el layout del mensaje. */
.shared-media-photo {
  max-width: 280px;
  max-height: 320px;
  border-radius: var(--radius-md);
  display: block;
  object-fit: contain;
}
.shared-media-video {
  max-width: 280px;
  max-height: 320px;
  border-radius: var(--radius-md);
  display: block;
}
.shared-youtube {
  cursor: pointer;
  max-width: 280px;
}
.shared-youtube-thumb {
  position: relative;
  border-radius: var(--radius-md);
  overflow: hidden;
}
.shared-youtube-thumb img {
  width: 100%;
  display: block;
}
.shared-youtube-thumb iframe {
  width: 100%;
  aspect-ratio: 16 / 9;
  border: none;
  display: block;
}
.shared-youtube-play {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 2rem;
  color: #fff;
  background: rgba(0, 0, 0, 0.35);
}
.shared-youtube-title {
  font-size: 0.85rem;
  margin: 0.3rem 0 0;
}

/* Aviso de "Fulano entró/salió de la sala" (2026-07-21) */
.msg-system {
  text-align: center;
  color: var(--text-muted);
  font-size: 0.78rem;
  padding: 0.2rem 0;
}

/* Sala de Karaoke (2026-07-21) */
.karaoke-player-wrap {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  background: #000;
  border-radius: var(--radius-md);
  overflow: hidden;
}
.karaoke-player-wrap iframe,
#karaoke-player {
  width: 100%;
  height: 100%;
}

/* Chat + regalos flotando sobre el video, estilo TikTok Live (2026-07-21) */
.karaoke-chat-overlay {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  max-height: 55%;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  gap: 0.2rem;
  padding: 0.5rem 0.6rem;
  background: linear-gradient(to top, rgba(0, 0, 0, 0.7), rgba(0, 0, 0, 0));
  pointer-events: none;
}
.karaoke-chat-overlay-msg {
  font-size: 0.78rem;
  line-height: 1.3;
  color: #fff;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.9);
  animation: karaoke-overlay-in 0.2s ease-out;
}
.karaoke-chat-overlay-msg strong {
  color: var(--accent-cyan);
}
.karaoke-chat-overlay-msg.fading {
  opacity: 0;
  transition: opacity 1.5s ease;
}
@keyframes karaoke-overlay-in {
  from { opacity: 0; transform: translateY(6px); }
  to { opacity: 1; transform: translateY(0); }
}
.karaoke-now-playing {
  font-size: 0.9rem;
  color: var(--text-secondary);
  margin: 0;
}
.karaoke-mic-row {
  display: flex;
}
#karaoke-mic-btn {
  font-weight: 600;
}
.karaoke-controls {
  display: flex;
  gap: 0.5rem;
}
.karaoke-queue-heading {
  margin: 0.4rem 0 0;
  font-size: 0.85rem;
  color: var(--text-secondary);
}
.karaoke-queue-list {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
}
.karaoke-queue-row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  background: var(--bg-surface-2);
  border-radius: var(--radius-sm);
  padding: 0.4rem 0.6rem;
}
.karaoke-queue-title {
  flex: 1;
  font-size: 0.85rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.karaoke-queue-title small {
  color: var(--text-muted);
}

/* ============================================================
   Personalización de fuentes/estilos (2026-07-23, pedido explícito
   con imágenes de referencia de otra app: letras.png, perfil.png,
   leter-perfil.png)
   ============================================================ */

/* Nick clickeable dentro de un mensaje de chat (2026-07-23) — mismo menú
   (ver perfil/privado/regalo) que ya funcionaba desde el roster/en línea. */
.msg-sender-name {
  cursor: pointer;
}
.msg-sender-name:hover .decorated-name {
  text-decoration: underline;
}

/* Vista previa del modal de "Fuente del chat" (botón "+"). */
.chat-style-preview {
  background: var(--bg-surface-2);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-md);
  padding: 0.9rem;
  margin: 0.4rem 0 0.9rem;
  font-size: 1.05rem;
  word-break: break-word;
}

/* Picker de estilo de tarjeta de perfil — mismo patrón visual que
   .badge-option (círculo con un puntito de color adentro en vez de un
   ícono), para no inventar un componente nuevo. */
.style-option {
  border-width: 2px;
}
.style-swatch-dot {
  display: block;
  width: 16px;
  height: 16px;
  border-radius: 50%;
}

/* Borde/glow de estilo de perfil sobre el .modal de "ver perfil" — variable
   CSS por instancia (--profile-style-color), puesta desde JS
   (applyProfileStyleToModal en avatar.js). */
.modal.profile-style-active {
  border: 2px solid var(--profile-style-color);
  box-shadow: 0 0 18px var(--profile-style-color);
}
.modal.profile-style-blink {
  animation: profile-style-pulse 1.8s ease-in-out infinite;
}
@keyframes profile-style-pulse {
  0%, 100% { box-shadow: 0 0 8px var(--profile-style-color); }
  50% { box-shadow: 0 0 26px var(--profile-style-color); }
}

/* Fondo decorativo de la tarjeta de perfil (2026-07-23) — puesto como
   background inline por JS (applyCardBackgroundToModal, avatar.js); acá
   solo un leve ajuste para que el contenido siga siendo legible encima. */
.modal.has-card-background .modal-header,
.modal.has-card-background .modal-footer {
  background: rgba(0, 0, 0, 0.2);
}

/* Picker de fondo de tarjeta — mismo patrón que .style-option, pero
   mostrando una franja real del gradiente en vez de un puntito sólido. */
.cardbg-option {
  padding: 2px;
}
.cardbg-swatch {
  display: block;
  width: 34px;
  height: 34px;
  border-radius: var(--radius-sm);
}

/* Regalos recibidos en "ver perfil" (2026-07-23). */
.view-profile-gifts-section {
  margin-top: 0.75rem;
  text-align: center;
}
.view-profile-gifts-heading {
  font-size: 0.8rem;
  color: var(--text-secondary);
  margin-bottom: 0.5rem;
}
.view-profile-gifts-grid {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.4rem;
}
.view-profile-gift-icon {
  width: 32px;
  height: 32px;
  object-fit: contain;
}

/* Overlay de efecto de entrada — animal/vehículo de la tienda al unirse a
   una sala (2026-07-24). Mismo z-index que .gift-overlay-container (ambos
   son celebratorios y efímeros, pointer-events:none, no compiten porque
   ocupan zonas distintas de la pantalla). */
.entrance-effect-overlay {
  position: fixed;
  inset: 0;
  z-index: 9999;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.4s ease;
}
.entrance-effect-overlay.entrance-effect-show {
  opacity: 1;
}
.entrance-effect-inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.7rem;
}
.entrance-effect-img {
  width: min(45vw, 280px);
  height: min(45vw, 280px);
  object-fit: contain;
  filter: drop-shadow(0 0 30px rgba(255, 255, 255, 0.35));
}
.entrance-effect-overlay.entrance-effect-show .entrance-effect-img {
  animation: entrance-effect-bounce 0.6s cubic-bezier(0.34, 1.56, 0.64, 1), entrance-effect-shake 0.3s ease 0.5s;
}
.entrance-effect-caption {
  margin: 0;
  padding: 0.4rem 1.1rem;
  border-radius: 999px;
  background: rgba(0, 0, 0, 0.55);
  color: #fff;
  font-weight: 700;
  font-size: 0.95rem;
  backdrop-filter: blur(4px);
}
@keyframes entrance-effect-bounce {
  0% { transform: scale(0.3); opacity: 0; }
  60% { transform: scale(1.2); opacity: 1; }
  100% { transform: scale(1); opacity: 1; }
}
@keyframes entrance-effect-shake {
  0%, 100% { transform: translate(0, 0); }
  20% { transform: translate(-6px, 0); }
  40% { transform: translate(6px, 0); }
  60% { transform: translate(-4px, 0); }
  80% { transform: translate(4px, 0); }
}
@media (prefers-reduced-motion: reduce) {
  .entrance-effect-overlay.entrance-effect-show .entrance-effect-img {
    animation: none;
  }
}
