:root{
  --bg-0:#070a10;
  --bg-1:#0b0f18;
  --card:#0f1523;
  --card-detail:linear-gradient(180deg, rgba(255,255,255,.04), rgba(255,255,255,.01));
  --text:#ffffff;
  --bs-heading-color-h1:#ffffff;
  --muted:#a7b0c2;
  --border:rgba(255,255,255,.2);
  --border-hover: rgba(255,255,255,.2);
  --shadow: 0 20px 50px rgba(0,0,0,.25);
  --shadow-hover: 0 25px 75px rgba(0,0,0,.50);
  --ads:#fc720b;
  --joven:#f42c2c;
  /* Colores semánticos estandarizados (éxito/advertencia/error) — mismos tonos base que ya usan
     .badge-neon-green/-yellow/-red (ver css más abajo), para no repetir hex "inventados" en cada
     componente nuevo que necesite un verde/amarillo/rojo de estado. */
  --color-success:#22c55e;
  --color-warning:#eab308;
  --color-error:#f42c2c;
  --radius: 20px;
  /* Escala de radios unificada (auditoría): sm=controles pequeños, md=inputs/controles, lg=tarjetas (=--radius), xl=modales/superficies grandes */
  --radius-sm: 10px;
  --radius-md: 14px;
  --radius-lg: var(--radius);
  --radius-xl: 26px;
  /* Escala tipográfica unificada (auditoría) */
  --fs-xs: .75rem;
  --fs-sm: .85rem;
  --fs-md: .95rem;
  --fs-lg: 1.05rem;
}
[data-theme="light"]{
  --bg-0:#f6f8ff;
  --bg-1:#ffffff;
  --card:rgba(255,255,255,.55);
  --card-detail:linear-gradient(180deg, rgba(255,255,255,.04), rgba(255,255,255,.01));
  --text:#0c1220;
  --bs-heading-color-h1:#000000;
  --muted:#4e5a72;
  --border:rgba(0,0,0,.2);
  --border-hover:rgba(0,0,0,.3);
  --shadow: 0 20px 50px rgba(244,44,44,.15);
  --shadow-hover: 0 25px 75px rgba(255,255,255,.30);
}

html,body{
  height:100%;
  max-width: 100%;
  overflow-x: hidden;
}
body{
  /* Geist (Google Fonts, cargada en el <head> de cada página) con la pila de system-ui de antes
     como respaldo por si el CDN de fuentes no carga (offline, bloqueado, etc.). */
  font-family: "Geist", system-ui, -apple-system, Segoe UI, Roboto, Arial, "Noto Sans", "Helvetica Neue", sans-serif;
  background: radial-gradient(1200px 800px at 20% -10%, rgba(252,114,11,.22), transparent 55%),
              radial-gradient(900px 700px at 95% 20%, rgba(244,44,44,.18), transparent 55%),
              linear-gradient(180deg, var(--bg-0), var(--bg-1));
  color: var(--text) !important;
  overflow-x: hidden;
}
a {
  color: inherit;
  text-decoration: none;
}
/* Dificulta guardar/arrastrar cualquier imagen de la app (fotos de discípulos, escudos, etc.) —
   quita el menú de "Guardar en Fotos" al mantener presionado en iOS/Android (-webkit-touch-callout),
   evita seleccionarla como texto/arrastrarla al escritorio. Se complementa con la prevención de
   clic derecho y de arrastre en app.js (ver el listener global de "contextmenu"/"dragstart").
   Aviso honesto: esto NO es infalible — una captura de pantalla del dispositivo siempre funciona,
   y no hay forma de evitar eso desde una página web — pero sí bloquea el atajo directo de guardado
   que se reportó (mantener presionado, clic derecho, arrastrar). */
img{
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  user-select: none;
  -webkit-user-drag: none;
  user-drag: none;
}
.small-muted{color: var(--muted);}
/* Bootstrap pinta .form-text con su gris por defecto (#6c757d), casi ilegible sobre el fondo
   oscuro del panel de administración. Lo alineamos al mismo tono que .small-muted. */
.form-text{color: var(--muted);}
.name-joven{color: var(--text);}

/* Auditoría de accesibilidad: foco visible consistente en toda la app */
.btn-neon:focus-visible,
.btn-grad:focus-visible,
.btn-danger-outline:focus-visible,
.search:focus-visible,
a:focus-visible,
.card-person:focus-visible{
  outline: 2px solid var(--ads);
  outline-offset: 2px;
}

.navbar-brand{
  color: var(--text) !important;
}

/* Transparente en la parte de arriba de la página; en cuanto se hace scroll (clase .is-scrolled,
   ver initNavbarScroll() en app.js) recupera la superficie de cristal oscura para que el texto
   siga siendo legible sobre el contenido que pasa detrás. */
.navbar-glass{
  background: transparent;
  transition: background .2s ease, backdrop-filter .2s ease;
}
.navbar-glass.is-scrolled{
  /* .55 se quedaba corto en móvil (backdrop-filter va desactivado ahí, ver la media query de la
     barra de tabs de abajo): con tarjetas de colores fuertes pasando detrás, el "Administrador"/
     nombre del grupo en blanco perdía contraste. Más opaco para que siga leyéndose bien encima de
     cualquier cosa. */
  background: rgba(10,14,22,.85);
  backdrop-filter: blur(10px);
}
[data-theme="light"] .navbar-glass.is-scrolled{ background: rgba(255,255,255,.88); }

/* El navbar reparte 3 zonas (logo | menú | tema+notif+cuenta) en un grid de 3 columnas en vez del
   flex space-between de Bootstrap — con space-between, la zona del medio no queda geométricamente
   centrada cuando el bloque de la izquierda (logo) y el de la derecha (tema+cuenta) no pesan lo
   mismo. El grid centra .navbar-menu-group siempre, sin importar el ancho de los otros dos. */
.navbar > .container{
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
}
.navbar > .container > .navbar-brand{ justify-self: start; }
.navbar > .container > .navbar-menu-group{ justify-self: center; margin-left: 0; margin-right: 0; }
.navbar > .container > #navAccountBox{ justify-self: end; margin-left: 0; }
@media (max-width: 991.98px){
  /* Con las 3 zonas envueltas (flex-wrap) en pantallas chicas, el grid de 3 columnas ya no aplica
     — vuelve a apilarse como bloques normales, uno debajo del otro. */
  .navbar > .container{ display: flex; flex-wrap: wrap; }
}

@media (max-width: 576px){
  .navbar .btn-neon{
    padding: .5rem .75rem;
    font-size: .9rem;
    white-space: nowrap;
  }
  .navbar .fw-bold{
    font-size: 1rem;
  }
}

/* Fondo sutil del body en Torneo > Equipos: una imagen (img/bg-team/) por equipo, detrás de todo
   el contenido, que hace crossfade al cambiar de pestaña — ver setTeamBackground() en app.js. Dos
   capas superpuestas (en vez de solo cambiar background-image, que no se puede animar) para que la
   saliente se desvanezca mientras entra la nueva, igual que el crossfade de fotos de las tarjetas. */
.team-bg-layer{
  position: fixed;
  inset: 0;
  z-index: -1;
  background-size: cover;
  background-position: center top;
  opacity: 0;
  transition: opacity .6s ease;
  pointer-events: none;
}
.team-bg-layer.is-active{ opacity: .16; }
[data-theme="light"] .team-bg-layer.is-active{ opacity: .09; }

.brand-dot{
  width: 10px; height: 10px; border-radius: 50%;
  background: linear-gradient(90deg, var(--ads), var(--joven));
  box-shadow: 0 0 18px rgba(252,114,11,.35), 0 0 18px rgba(244,44,44,.25);
}

.hero{ padding-top: 2rem; padding-bottom: 1.5rem; }
.hero h1{ letter-spacing: -.02em; font-weight: 800; color: var(--bs-heading-color-h1); }
.hero .sub{ font-weight: 200; color: var(--text); }

/* Breadcrumb en la parte de arriba de cada sección (usuario, auxiliar y admin) — ver
 * initBreadcrumb()/BREADCRUMB_MAP en app.js, que llena el <ol id="pageBreadcrumb"> vacío que ya
 * trae cada página. */
.app-breadcrumb-nav{ margin-bottom: .85rem; }
.breadcrumb{ margin-bottom: 0; font-size: .82rem; }
.breadcrumb-item{ color: var(--muted); }
.breadcrumb-item a{ color: var(--muted); text-decoration: none; }
.breadcrumb-item a:hover{ color: var(--text); text-decoration: underline; }
.breadcrumb-item.active{ color: var(--text); font-weight: 600; }
.breadcrumb-item + .breadcrumb-item::before{ color: var(--muted); content: "/"; }

.glow{
  position: absolute; inset: -180px -180px auto auto;
  width: 420px; height: 420px;
  background: radial-gradient(circle at 30% 25%, rgba(252,114,11,.35), transparent 60%);
  filter: blur(24px);
  pointer-events:none; opacity:.9;
  max-width: 100%;
}
.glow2{
  position: absolute; inset: auto auto -220px -240px;
  width: 520px; height: 520px;
  background: radial-gradient(circle at 35% 30%, rgba(244,44,44,.25), transparent 60%);
  filter: blur(28px);
  pointer-events:none; opacity:.9;
  max-width: 100%;
}
/* En login.html el contenedor de .glow/.glow2 es un div corto (solo la tarjeta de acceso), a
   diferencia de index.html/detail.html donde queda dentro de una página larga y el sangrado
   negativo de estos círculos decorativos pasa desapercibido. Ahí sí se nota: agrega scroll de
   verdad (la página crece más alto/ancho que el viewport). Se recorta solo en este wrapper. */
.login-glow-clip{ overflow: hidden; }

[data-theme="light"] .card-person{ background: var(--card-detail);}
.card-person:hover{
  border-color: var(--border-hover);
  box-shadow: var(--shadow-hover);
}
[data-theme="light"] .card-person:hover{ box-shadow: 0 22px 60px rgba(10,20,40,.16); }

.avatar{ aspect-ratio: 1/1; width: 100%; object-fit: cover; background: rgba(255,255,255,.02);}

/* Desvanecido sutil en la parte de abajo de las fotos (tarjetas del directorio y foto grande del
   detalle): que se sienta fusionada con la tarjeta en vez de cortada de golpe. Arranca casi al
   final (92%, antes 82%) — con el escudo del equipo detrás de la foto (.team-watermark, más abajo)
   ya sirviéndose como imagen real en vez de estar roto/invisible, ese 18% de desvanecido dejaba
   asomarse demasiado escudo por debajo de la foto de perfil; un 8% más angosto sigue sintiéndose
   fundido con la tarjeta sin transparentar tanto escudo. */
.avatar, .photo-fade-bottom{
  -webkit-mask-image: linear-gradient(to bottom, #000 92%, transparent 100%);
  mask-image: linear-gradient(to bottom, #000 92%, transparent 100%);
}

/* Segunda foto de la tarjeta del directorio (opcional, "hover_photo_path"): crossfade real entre
   las dos fotos (la de perfil se desvanece a 0 mientras la segunda aparece), no una superposición
   simple — ambas suelen ser PNG con fondo transparente, así que superponerlas sin apagar la de
   abajo dejaba ver las dos a la vez. En escritorio (dispositivos con hover real) el crossfade
   ocurre con :hover puro en CSS; en móvil/touch la clase .is-active la activa un
   IntersectionObserver mientras la tarjeta pasa por el centro de la pantalla al hacer scroll (ver
   initCardPhotoSwapObserver en app.js). El fade-out de la foto principal solo aplica si esa
   tarjeta en particular tiene segunda foto (:has), para no apagar la única foto de las demás.
   Nunca se usa fuera del directorio. */
.card-photo-swap{ position: relative; overflow: hidden; }
.avatar-main{ position: relative; z-index: 1; transition: opacity .45s ease; }
.avatar-hover{
  position: absolute; inset: 0; z-index: 1;
  opacity: 0;
  transition: opacity .45s ease;
  pointer-events: none;
}
@media (hover: hover){
  .card-person:hover .avatar-hover{ opacity: 1; }
  .card-person:hover .card-photo-swap:has(.avatar-hover) .avatar-main{ opacity: 0; }
}
.card-photo-swap.is-active .avatar-hover{ opacity: 1; }
.card-photo-swap.is-active .avatar-main{ opacity: 0; }

/* Escudo del equipo, muy tenue, por debajo de la foto (z-index:0 contra z-index:1 de las fotos):
   solo se asoma donde la foto se desvanece hacia abajo (ver mask-image de .avatar). Blanco casi
   transparente en reposo; al hacer hover en la tarjeta se pinta del color real del equipo, sin
   llegar a opacidad completa para que siga sintiéndose sutil y no le quite protagonismo a la foto. */
.team-watermark{
  position: absolute; inset: 0; z-index: 0;
  display: flex; align-items: center; justify-content: center;
  pointer-events: none;
}
.team-watermark-img{
  width: 75%; height: 75%;
  background-color: rgba(255,255,255,.14);
  -webkit-mask-image: var(--icon-url); mask-image: var(--icon-url);
  -webkit-mask-size: contain; mask-size: contain;
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-position: center; mask-position: center;
  transition: background-color .35s ease;
}
.team-watermark-emoji{
  font-size: 4.2rem; line-height: 1;
  opacity: .14;
  filter: grayscale(1) brightness(1.8);
  transition: opacity .35s ease, filter .35s ease;
}
.card-person:hover .team-watermark-img{ background-color: color-mix(in srgb, var(--team-color) 55%, transparent); }
.card-person:hover .team-watermark-emoji{ opacity: .45; filter: none; }

/* El badge de grupo va explícitamente por ENCIMA de la foto (z-index:2, más alto que el z-index:1
   de .avatar-main/.avatar-hover) para el efecto de profundidad: foto → escudo de fondo → badge. */
.card-photo-badge{ z-index: 2; }

/* Vista "Equipos" (Torneo > Equipos) — cambios exclusivos de esta vista, no del directorio: el
   hover pinta el borde del color REAL del equipo (todas las tarjetas de esta grilla son del mismo
   equipo, a diferencia del directorio que usa el color del grupo), y el nombre + los demás datos
   del card-person van centrados. */
#teamsGrid .card-person:hover{ border-color: var(--team-color, var(--border)); }
/* "Mi equipo" (#miEquipoGrid) reusa el mismo estilo de tarjeta que "Equipos" — apodo centrado en
   grande y el renglón de lugar/puntos también centrado debajo. */
#teamsGrid .card-person .p-3,
#miEquipoGrid .card-person .p-3{ text-align: center; }
/* Apodo en grande, sobrepuesto un poco encima de la foto de perfil (margen negativo + position
   relative para poder ganarle en apilamiento a la foto sin salirse de la tarjeta, que ya recorta
   con overflow:hidden). Line-height ajustado para que quepa cómodo aun a 55px. */
#teamsGrid .card-person .teams-nickname,
#ahorroBottom .card-person .teams-nickname,
#miEquipoGrid .card-person .teams-nickname{
  position: relative;
  z-index: 2;
  margin-top: -34px;
  font-size: 45px;
  font-weight: 800;
  line-height: 1.1;
}
/* Card del auxiliar en "Mi ahorro": altura fija (a diferencia de h-100) para que NO crezca cuando
   se expande una pregunta del acordeón de al lado — las dos tarjetas ya no comparten altura. Sin
   overflow:hidden extra en .p-3 (el .card-person ya recorta las esquinas) para no cortar el
   "Auxiliar de grupo"; el alto se dio con margen suficiente para foto + las dos líneas de texto. */
.ahorro-aux-card{ height: 380px; }
.ahorro-aux-card .card-person-inner{ display: flex; flex-direction: column; }
.ahorro-aux-card .card-photo-swap{ flex-shrink: 0; }
.ahorro-aux-card .p-3{ flex: 1 1 auto; min-height: 0; }
/* Apodo grande al mismo tamaño/peso que en la tarjeta de "Equipos", pero para el detalle de la
   persona: va ANTES del lugar/puntos y el badge de equipo en el orden de la tarjeta, pero por
   encima de las dos fotos (principal y hover) en posición Z — no como bloque aparte debajo de
   ellas — por eso, igual que #teamsGrid .card-person .teams-nickname de arriba, con
   position:relative y margin-top negativo para sobreponerse a la foto en vez de empujarla. */
.nickname-display{
  position: relative;
  z-index: 2;
  margin-top: -34px;
  font-size: 45px;
  font-weight: 800;
  line-height: 1.1;
}
/* Detalle de la persona: a diferencia de las tarjetas del directorio/equipos (que solo pintan el
   escudo del color del equipo al hacer hover), aquí no hay hover posible — así que va siempre
   coloreado del color del equipo, no en blanco tenue. */
#detailPhotoWrap .team-watermark-img{ background-color: color-mix(in srgb, var(--team-color) 55%, transparent); }
#detailPhotoWrap .team-watermark-emoji{ opacity: .45; filter: none; }

/* Tarjeta "escudo" del equipo, primera de la grilla de "Equipos" (antes que la del capitán):
   mismo tamaño/columna que .card-person pero con look de letrero de neón parpadeante del color
   real del equipo — escudo grande, lugar en el ranking general y puntos totales aportados. */
.card-team{
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: .6rem;
  text-align: center;
  padding: 1.75rem 1rem;
  background: radial-gradient(circle at 50% 28%, color-mix(in srgb, var(--team-color) 20%, transparent), transparent 72%);
  border: 1px solid color-mix(in srgb, var(--team-color) 65%, transparent);
  border-radius: var(--radius);
  box-shadow: 0 0 22px color-mix(in srgb, var(--team-color) 45%, transparent),
              inset 0 0 28px color-mix(in srgb, var(--team-color) 12%, transparent);
}
/* Selector compuesto (dos clases) a propósito: le gana en especificidad a .fade-in de forms.css
   más abajo en el archivo, que de otro modo pisaría por completo este "animation" shorthand (la
   tarjeta también trae la clase fade-in de entrada, igual que el resto de tarjetas de la grilla).
   Encadena las dos animaciones: primero el fade-in de entrada, y ya asentada, el parpadeo en loop. */
.card-team.fade-in{
  animation: fadeInUp .55s ease forwards, cardTeamFlicker 3.2s ease-in-out .55s infinite;
}
.card-team .card-team-shield{
  width: 150px;
  height: 150px;
  border-radius: 22px;
  display: flex;
  align-items: center;
  padding: 20px;
  justify-content: center;
  background: color-mix(in srgb, var(--team-color) 15%, transparent);
  filter: drop-shadow(0 0 10px color-mix(in srgb, var(--team-color) 20%, transparent));
}
.card-team .card-team-shield img{ width: 100%; height: 100%; object-fit: cover; border-radius: 22px; }
.card-team .card-team-shield .team-icon-emoji{ font-size: 3rem; }
.card-team .card-team-rank{
  font-size: 1.6rem;
  font-weight: 800;
  color: var(--team-color);
  text-shadow: 0 0 12px color-mix(in srgb, var(--team-color) 75%, transparent);
}
.card-team .card-team-points{
  font-size: .95rem;
  font-weight: 600;
  color: var(--text);
  opacity: .85;
}
/* Parpadeo estilo letrero de neón: intervalos irregulares (no un pulso parejo) para que se sienta
   como un tubo de neón real fallando, no como una animación de opacidad genérica. */
@keyframes cardTeamFlicker{
  0%, 3%, 6%, 82%, 85%, 100%{
    box-shadow: 0 0 22px color-mix(in srgb, var(--team-color) 45%, transparent),
                inset 0 0 28px color-mix(in srgb, var(--team-color) 12%, transparent);
    opacity: 1;
  }
  4%, 84%{ box-shadow: 0 0 6px color-mix(in srgb, var(--team-color) 15%, transparent); opacity: .78; }
  5%{ box-shadow: 0 0 30px color-mix(in srgb, var(--team-color) 60%, transparent); opacity: 1; }
}

/* Avatar chico para la columna "Nombre" de las tablas: foto real si la persona tiene, si no un
   círculo neutro con ícono genérico. */
.avatar-sm{
  width: 28px; height: 28px; border-radius: 50%; object-fit: cover; flex-shrink: 0;
  background: rgba(255,255,255,.08); border: 1px solid var(--border);
}
.avatar-sm-fallback{
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--muted); font-size: .85rem;
}
.name-cell{ display: inline-flex; align-items: center; gap: .5rem; }
/* Borde del color del grupo en el avatar chico de las tablas — reemplaza el nombre/apodo
   coloreado de antes como forma de identificar a qué grupo pertenece cada quien. */
.avatar-group-border{ border: 1px solid var(--group-color, var(--border)); cursor: pointer; }

/* Tarjeta flotante de vista previa (hover sobre avatar/nombre en tablas y en el dropdown de
   cuenta) — un solo elemento fijo compartido, ver initPersonPreview()/showPersonPreview() en
   app.js. Mismo criterio de tamaño/estilo que .card-person, pero fijo en vez de en flujo. */
.person-preview-card{
  position: fixed;
  width: 220px;
  /* Opaco a propósito (--bg-1, no --card-detail): --card-detail es un degradado casi
     transparente pensado para ir ENCIMA del fondo ya opaco de la página, no para ser el único
     fondo de un elemento flotante — con eso se veía prácticamente transparente. */
  background: var(--bg-1);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  box-shadow: 0 20px 50px rgba(0,0,0,.45);
  overflow: hidden;
  opacity: 0;
  transform: translateY(4px);
  pointer-events: none;
  transition: opacity .12s ease, transform .12s ease;
  z-index: 99999;
}
.person-preview-card.show{ opacity: 1; transform: translateY(0); pointer-events: auto; }

/* Acordeón de preguntas frecuentes (p.ej. "Mi ahorro"): Bootstrap trae estilos claros por
   default, aquí se adapta al tema oscuro/claro del sistema. */
.accordion-item{
  background: transparent;
  border: 1px solid var(--border);
  border-radius: var(--radius-md) !important;
  margin-bottom: .5rem;
  overflow: hidden;
}
.accordion-item:last-child{ margin-bottom: 0; }
.accordion-button{
  display: flex;
  align-items: center;
  background: rgba(255,255,255,.03);
  color: var(--text);
  font-weight: 700;
  box-shadow: none !important;
}
.accordion-button:not(.collapsed){ background: rgba(255,255,255,.06); color: var(--text); }
.accordion-body{ background: transparent; color: var(--text); }
/* El chevron es el mismo botón redondo que el de colapsar/expandir filtros (.filter-collapse-icon),
   en vez de la flecha por default de Bootstrap. */
.accordion-button::after{ display: none; content: none; }
.accordion-button:hover .filter-collapse-icon{ background: rgba(255,255,255,.12); }
.accordion-button:not(.collapsed) .filter-collapse-icon{ transform: rotate(180deg); }

/* Columna "Nombre" con ancho fijo en todas las tablas que la tienen, para que no se ensanche o
   achique según el nombre más largo de la página actual y las demás columnas queden alineadas. */
.col-name{ width: 335px; min-width: 335px; }

.badge-soft{
  display: inline-flex;
  align-items: center;
  gap: .35rem;

  padding: .15rem .5rem;
  border-radius: 999px;

  /* legibilidad */
  color: var(--text);
  font-weight: 200;
  letter-spacing: .2px;

  /* “glass” pero con contraste suficiente */
  background: var(--card-detail);
  border: 1px solid var(--border);

  /* blur */
  -webkit-backdrop-filter: blur(10px) saturate(140%);
  backdrop-filter: blur(10px) saturate(140%);

  box-shadow:
    0 3px 15px rgba(0,0,0,.05),
    inset 0 1px 0 rgba(255,255,255,.08);
}

.badge-soft:hover{
  border:1px solid var(--border-hover) !important;
}

/* Colores de badge/nombre por grupo (Adolescentes/Jóvenes): toman --group-color cuando el
   renderizador lo trae del catálogo editable de grupos (admin > Aula > Grupos); si no,
   conservan el naranja/rojo de siempre como respaldo. */
.badge-ads{ background: color-mix(in srgb, var(--group-color, var(--ads)) 30%, transparent);
  border-color: color-mix(in srgb, var(--group-color, var(--ads)) 75%, transparent);
  color: var(--text);}
.badge-joven{ background: color-mix(in srgb, var(--group-color, var(--joven)) 30%, transparent);
  border-color: color-mix(in srgb, var(--group-color, var(--joven)) 75%, transparent);
  color: var(--text);}
/* Igual que .badge-ads/.badge-joven, para grupos "administrativos" (p.ej. "Encargado") — ver
   .badge-neon-dynamic más abajo, misma idea aplicada a la variante badge-soft de groupBadgeAbbr(). */
.badge-dynamic{ background: color-mix(in srgb, var(--group-color, var(--joven)) 30%, transparent);
  border-color: color-mix(in srgb, var(--group-color, var(--joven)) 75%, transparent);
  color: var(--text);}

/* Colores de nombre por grupo */
.name-ads {
  color: var(--group-color, var(--ads));
}

.name-joven {
  color: var(--group-color, var(--joven));
}

/* Padding uniforme para todos los botones del sistema, sin importar btn-sm u otras variantes */
.btn{
  padding: .58rem .95rem !important;
}

.btn-neon{
  border: 1px solid var(--border) !important;
  background: rgba(255,255,255,.1) !important;
  color: var(--text) !important;
  border-radius: 999px !important;
  padding: .55rem .9rem !important;
  transition: transform .2s ease, filter .2s ease, background .2s ease, border-color .2s ease !important;
}
.btn-neon:hover{
  filter: brightness(1.1) !important;
  border-color: var(--border-hover);
  background: rgba(255,255,255,.2) !important;
}
/* Estado activo de CUALQUIER pestaña del sistema (regla única y genérica: cubre admin, mainTabs,
   personFormTabs, accountTabs, profileTabs, aulaSubTabs, teamsSubTabs, tasksSubTabs, y cualquier
   lista de pills futura) — antes estaba duplicada y dispersa por ID y varios tabs nuevos no lo tenían. */
/* --group-color-1/2/3 los fija applyGroupThemeVars() en <html> según el grupo (adolescentes/
   jóvenes) de quien inició sesión — solo en las vistas de usuario/auxiliar (ver
   renderAccountDropdown() en app.js); admin/login/legal nunca las fijan, así que ahí el
   var(...,fallback) siempre cae al degradado naranja/rojo fijo de toda la vida. */
.btn-neon.active,
.dropdown-toggle.active{
  background: linear-gradient(90deg, var(--group-color-1, var(--ads)), var(--group-color-3, var(--joven))) !important;
  border-color: transparent !important;
  color: #ffffff !important;
  font-weight: 800 !important;
  box-shadow: 0 10px 30px color-mix(in srgb, var(--group-color-1, var(--ads)) 18%, transparent),
              0 12px 32px color-mix(in srgb, var(--group-color-3, var(--joven)) 12%, transparent);
}
[data-theme="light"] .btn-neon.active,
[data-theme="light"] .dropdown-toggle.active{ color: #08111e !important; }

/* Botones del nav-pill del navbar (Inicio/Directorio/Torneo/etc.): en reposo solo se ve el
   ícono+texto, sin borde ni fondo — el borde/fondo de .btn-neon (línea ~487) solo aparece al
   pasar el mouse, para un menú más limpio cuando no se está interactuando con él. Escoped a
   .navbar-menu-group para no afectar el resto de usos de .btn-neon (sidebar de sistema, botones
   de acción de tablas, etc.); :not(.active) para no pisar el degradado del tab activo; y solo
   aplica en desktop porque el tab bar nativo de móvil (@media max-width 767.98px más abajo) ya
   fuerza su propio fondo transparente permanente. */
.navbar-menu-group .btn-neon:not(.active){
  background: transparent !important;
  border-color: transparent !important;
}
.navbar-menu-group .btn-neon:not(.active):hover{
  background: rgba(255,255,255,.1) !important;
  border-color: var(--border-hover) !important;
}
[data-theme="light"] .navbar-menu-group .btn-neon:not(.active):hover{ background: rgba(0,0,0,.05) !important; }

/* Botón "shiny" (basado en https://codepen.io/hexagoncircle/pen/MWMqXbK, recoloreado con las
   variables de la app en vez de la paleta negro/azul original): un anillo cónico animado que gira
   despacio siempre, un patrón de puntos que se asoma detrás y un brillo interno que se acelera al
   pasar el mouse/enfocar — todo con los mismos colores de grupo que ya usaba .btn-grad (--ads/
   --joven o --group-color-1/2/3 si la página los define), así que sigue viéndose "de la app", solo
   que ahora con vida. Los 4 @property registran los custom properties animables (sin esto,
   transicionar un ángulo/porcentaje/color a mitad de camino no es posible en CSS). */
@property --gradient-angle{ syntax:"<angle>"; initial-value:0deg; inherits:false; }
@property --gradient-angle-offset{ syntax:"<angle>"; initial-value:0deg; inherits:false; }
@property --gradient-percent{ syntax:"<percentage>"; initial-value:5%; inherits:false; }
@property --gradient-shine{ syntax:"<color>"; initial-value:white; inherits:false; }

.btn-grad{
  --shiny-highlight:#ffffff;
  --shiny-highlight-subtle: color-mix(in srgb, var(--group-color-2, var(--joven)) 55%, white);
  --animation: gradient-angle linear infinite;
  --duration: 3s;
  isolation: isolate;
  position: relative;
  overflow: hidden;
  border: 1px solid transparent;
  color: var(--text);
  font-weight: 800;
  border-radius: 999px;
  padding: .58rem .95rem;
  background: linear-gradient(90deg, var(--group-color-1, var(--ads)), var(--group-color-2, var(--joven)), var(--group-color-3, var(--ads))) padding-box,
    conic-gradient(from calc(var(--gradient-angle) - var(--gradient-angle-offset)),
      transparent,
      var(--shiny-highlight) var(--gradient-percent),
      var(--gradient-shine) calc(var(--gradient-percent) * 2),
      var(--shiny-highlight) calc(var(--gradient-percent) * 3),
      transparent calc(var(--gradient-percent) * 4)
    ) border-box;
  box-shadow: 0 10px 30px color-mix(in srgb, var(--group-color-1, var(--ads)) 18%, transparent),
              0 12px 32px color-mix(in srgb, var(--group-color-3, var(--joven)) 12%, transparent);
  transition: --gradient-angle-offset 800ms cubic-bezier(.25,1,.5,1),
              --gradient-percent 800ms cubic-bezier(.25,1,.5,1),
              --gradient-shine 800ms cubic-bezier(.25,1,.5,1);
}
.btn-grad:active{ translate: 0 1px; }

/* Puntos (se asoman solo en un arco que gira, detrás del texto) y brillo interno (barrido diagonal
   que gira sobre sí mismo) — mismas dos capas que el pen original, sin el ::before extra en un
   <span> interno (nuestros botones no envuelven su texto en <span>, así que ese brillo-respiración
   puntual del pen se deja fuera; el anillo+puntos+brillo ya dan el efecto "shiny" principal). */
.btn-grad::before,
.btn-grad::after{
  content:"";
  pointer-events:none;
  position:absolute;
  inset-inline-start:50%;
  inset-block-start:50%;
  translate:-50% -50%;
  z-index:-1;
}
.btn-grad::before{
  --size: calc(100% - 2px * 3);
  --dot-position: 2px;
  --dot-space: calc(var(--dot-position) * 2);
  width: var(--size); height: var(--size);
  background: radial-gradient(circle at var(--dot-position) var(--dot-position), white calc(var(--dot-position) / 4), transparent 0) padding-box;
  background-size: var(--dot-space) var(--dot-space);
  background-repeat: space;
  -webkit-mask-image: conic-gradient(from calc(var(--gradient-angle) + 45deg), black, transparent 10% 90%, black);
          mask-image: conic-gradient(from calc(var(--gradient-angle) + 45deg), black, transparent 10% 90%, black);
  border-radius: inherit;
  opacity: .35;
}
.btn-grad::after{
  --animation: shimmer linear infinite;
  width: 100%;
  aspect-ratio: 1;
  background: linear-gradient(-50deg, transparent, var(--shiny-highlight), transparent);
  -webkit-mask-image: radial-gradient(circle at bottom, transparent 40%, black);
          mask-image: radial-gradient(circle at bottom, transparent 40%, black);
  opacity: .5;
}
.btn-grad,
.btn-grad::before,
.btn-grad::after{
  animation: var(--animation) var(--duration),
    var(--animation) calc(var(--duration) / 0.4) reverse paused;
  animation-composition: add;
}
.btn-grad:hover{
  --gradient-percent: 20%;
  --gradient-angle-offset: 95deg;
  --gradient-shine: var(--shiny-highlight-subtle);
  background: linear-gradient(90deg, var(--group-color-3, var(--joven)), var(--group-color-2, var(--ads)), var(--group-color-1, var(--joven))) padding-box,
    conic-gradient(from calc(var(--gradient-angle) - var(--gradient-angle-offset)),
      transparent,
      var(--shiny-highlight) var(--gradient-percent),
      var(--gradient-shine) calc(var(--gradient-percent) * 2),
      var(--shiny-highlight) calc(var(--gradient-percent) * 3),
      transparent calc(var(--gradient-percent) * 4)
    ) border-box;
}
.btn-grad:focus-visible{
  --gradient-percent: 20%;
  --gradient-angle-offset: 95deg;
  --gradient-shine: var(--shiny-highlight-subtle);
}
.btn-grad:is(:hover,:focus-visible),
.btn-grad:is(:hover,:focus-visible)::before,
.btn-grad:is(:hover,:focus-visible)::after{
  animation-play-state: running;
}
@keyframes gradient-angle{ to{ --gradient-angle:360deg; } }
@keyframes shimmer{ to{ rotate:360deg; } }
[data-theme="light"] .btn-grad{ color: #ffffff; }
.btn-danger-outline{
  border: 1px solid rgba(244,44,44,.7) !important;
  border-radius: 999px !important;
  background: rgba(244,44,44,.1) !important;
  color: var(--text) !important;
}
.btn-danger-outline:hover{
  background: rgba(244,44,44,.24) !important;
  border-color: var(--joven) !important;
}

/* Botones de acción de las tablas: circulares, solo ícono (el texto se movió al tooltip de
   [data-tooltip], ver más abajo). Se combinan con los colores ya existentes (btn-neon-yellow,
   btn-neon-red, btn-neon, btn-grad) para no duplicar la paleta. */
.btn-icon-circle{
  display: inline-flex !important;
  align-items: center; justify-content: center;
  width: 34px; height: 34px;
  padding: 0 !important;
  border-radius: 50% !important;
  font-size: 1rem;
  line-height: 1;
  flex-shrink: 0;
}
.btn-icon-circle + .btn-icon-circle{ margin-left: .35rem; }

/* Tooltip propio del sistema: reemplaza tanto el title="" nativo del navegador (con su retraso y
   estilo del SO) como los tooltips de Bootstrap, para que todas las tablas usen el mismo look:
   burbuja oscura con flechita, aparece de inmediato al pasar el mouse o al enfocar con teclado.
   Se activa con el atributo data-tooltip="texto" en cualquier elemento (ver initAppTooltips en
   app.js). Es UN solo elemento compartido, con position:fixed y agregado a <body> — a propósito,
   NO un ::after/::before del propio elemento: esos, aunque invisibles (opacity:0), seguían
   contando en el scrollWidth de cualquier ancestro con overflow-x:auto (p.ej. .table-responsive),
   así que la pastilla de tooltip del botón de "Acciones" —pegado a la orilla derecha de la
   tabla— era la causa real del scroll horizontal no deseado en varias tablas. */
.app-tooltip{
  position: fixed;
  top: 0; left: 0;
  background: #16181f;
  color: #fff;
  padding: .45rem .75rem;
  border-radius: var(--radius-sm);
  font-size: var(--fs-xs);
  font-weight: 600;
  white-space: nowrap;
  box-shadow: 0 12px 30px rgba(0,0,0,.4);
  opacity: 0;
  pointer-events: none;
  transition: opacity .08s ease;
  z-index: 2000;
}
.app-tooltip::after{
  content: "";
  position: absolute;
  top: 100%;
  left: var(--tt-arrow-left, 50%);
  transform: translateX(-50%);
  border: 5px solid transparent;
  border-top-color: #16181f;
}
.app-tooltip.show{ opacity: 1; }

.search{
  border: 1px solid var(--border);
  background: rgba(255,255,255,.03);
  color: var(--text);
  border-radius: 999px;
  padding: .65rem 1rem;
}
.search:focus{
  outline: none;
  box-shadow: 0 0 0 .25rem rgba(252,114,11,.12);
  border-color: rgba(252,114,11,.35);
}

@media (max-width: 576px){
  .hero .badge-soft{
    text-align: center;
  }
}

.section-title{ display:flex; align-items:center; gap:.55rem; }
.section-title .chip{
  width: 10px; height: 10px; border-radius: 50%;
  background: linear-gradient(90deg, var(--ads), var(--joven));
  box-shadow: 0 0 16px rgba(252,114,11,.35), 0 0 16px rgba(244,44,44,.25);
}

.fade-in{ opacity: 0; transform: translateY(10px); animation: fadeInUp .55s ease forwards; }
@keyframes fadeInUp{ to{ opacity:1; transform: translateY(0);} }

.detail-card{
  border: 1px solid var(--border);
  background: var(--card-detail);
  border-radius: var(--radius);
}
[data-theme="light"] .detail-card{ background: var(--card-detail); }

.kv{ border-bottom: 1px dashed var(--border); padding: .6rem 0; }
.kv:last-child{ border-bottom: 0; }
/* Respuestas del detalle de un discípulo (ver kv()/kvHtml() en app.js): el valor se ve como un
   input deshabilitado (mismo borde/radio/fondo que .form-control), pero con tipografía blanca en
   vez del gris apagado que Bootstrap usa normalmente para :disabled, para que siga siendo legible
   como texto de solo lectura. */
.kv-value{
  display: block;
  min-height: 46px;
  padding: .55rem .9rem;
  margin-top: .3rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: rgba(255,255,255,.03);
  color: var(--text);
  font-weight: 600;
}
[data-theme="light"] .kv-value{ background: rgba(0,0,0,.02); }
/* "Mis hermanos son:"/"Mis amigos en el grupo son:" (ver personBadgesHtml() en app.js): en vez
   del recuadro de .kv-value, cada persona reconocida es una tarjetita de ancho fijo (col-2, ver
   el row que las envuelve en personBadgesHtml()) con avatar, apodo y grupo debajo en
   small-muted xs — borde y glow al hover del color de SU grupo (--group-color por elemento). */
.person-badges-row{ margin-top: .3rem; }
.person-badge{
  display: flex; flex-direction: column; align-items: center; text-align: center;
  width: 100%; padding: .6rem .4rem;
  /* Mismo grosor/opacidad que el borde del avatar de adentro (.person-badge-avatar) — antes era
     1px a 55% de opacidad contra el 2px sólido del avatar, y se veían dos tonos distintos del
     mismo color uno encima del otro. */
  border: 2px solid var(--group-color, var(--ads));
  border-radius: var(--radius-sm);
  background: rgba(255,255,255,.03);
  color: var(--text); text-decoration: none;
  transition: transform .15s ease, border-color .15s ease, box-shadow .15s ease;
}
a.person-badge:hover{
  transform: translateY(-2px);
  border-color: var(--group-color, var(--ads));
  box-shadow: 0 0 16px color-mix(in srgb, var(--group-color, var(--ads)) 45%, transparent);
}
[data-theme="light"] .person-badge{ background: rgba(0,0,0,.02); }
.person-badge-avatar{
  width: 60px; height: 60px; border-radius: 50%; object-fit: cover;
  border: 2px solid var(--group-color, var(--ads));
  margin-bottom: .35rem;
}
.person-badge-avatar--placeholder{
  display: flex; align-items: center; justify-content: center;
  background: rgba(255,255,255,.06); color: var(--muted); font-size: 1.3rem;
}
.person-badge-name{ font-weight: 700; font-size: 1rem; line-height: 1.15; }
.person-badge-group{ font-size: var(--fs-xs); }
.person-badge--disabled{ opacity: .7; }

.footer{ border-top: 1px solid var(--border); color: var(--muted); }
.footer-logo{ width: 60px; height: 60px; object-fit: contain; border-radius: 50%; }
.footer-heading{ font-size: .8rem; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; color: var(--text); margin-bottom: .65rem; }
.footer-link{ color: var(--muted); text-decoration: none; }
.footer-link:hover{ color: var(--text); text-decoration: none; }
.footer-social{ display: flex; flex-wrap: wrap; gap: .5rem; }
.footer-social a{
  width: 34px;
  height: 34px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: rgba(255,255,255,.05);
  border: 1px solid var(--border);
  color: var(--muted);
  text-decoration: none;
  transition: color .15s ease, border-color .15s ease, background .15s ease;
}
.footer-social a:hover{ color: var(--text); border-color: var(--ads); background: rgba(255,255,255,.09); }
/* "Mi iglesia" (iglesia.php): mismos íconos redondos del footer pero más grandes, y con un
   pequeño efecto de elevación al hover — la sección más prominente de la página. */
.footer-social-lg a{ width: 48px; height: 48px; font-size: 1.15rem; }
.footer-social-lg a:hover{ transform: translateY(-3px); }
.church-hero-logo{ width: 84px; height: 84px; flex-shrink: 0; }
/* Avatar del perfil del "Encargado" (encargado.php) — sin foto (no hay upload para este perfil),
   un ícono grande a modo de placeholder, mismo tamaño que el logo de "Mi iglesia". */
.leader-avatar{
  width: 84px; height: 84px; flex-shrink: 0; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-size: 3rem; color: var(--ads);
  background: color-mix(in srgb, var(--ads) 16%, transparent);
}
.church-hero-card .mini-kpi{ background: rgba(255,255,255,.05); }
[data-theme="light"] .church-hero-card .mini-kpi{ background: rgba(0,0,0,.04); }

/* Descripción de la iglesia ("Sobre nosotros", churchDescription en iglesia.php): mismo look que
   .mini-kpi (fondo sutil + radio chico + label pequeño) pero con clase propia a propósito, para
   no afectar .mini-kpi (que se usa en varias otras secciones con otro contenido/orden). */
.church-info-box{ background: rgba(255,255,255,.05); border-radius: var(--radius-sm); padding: .75rem 1rem; }
[data-theme="light"] .church-info-box{ background: rgba(0,0,0,.04); }
.church-info-label{ font-size: var(--fs-xs); color: var(--muted); margin-bottom: .35rem; }
/* Un renglón por horario de reunión de la iglesia (título + horario, ver church_schedules) dentro
   del mini-kpi "Horarios de reunión" — puede haber varios. */
.church-schedule-item{ font-weight: 600; }
.church-schedule-item + .church-schedule-item{ margin-top: .3rem; }

/* Formularios: misma superficie de cristal, bordes suaves y acentos del directorio. */
.form-label{
  color: var(--text);
  font-size: var(--fs-sm);
  font-weight: 600;
  letter-spacing: .02em;
  margin-bottom: .42rem;
}
/* Tabs del detalle/perfil del discípulo (Mi perfil > Datos generales/Hobbies/Gustos.../Fe/... y el
   modal de admin para crear/editar un discípulo): separación fija entre el título del label y su input. */
.p-tab .form-label,
.pf-panel .form-label{ margin-bottom: 5px; }
.form-control,
.form-select{
  min-height: 46px;
  border: 1px solid var(--border) !important;
  border-radius: var(--radius-md) !important;
  background: rgba(255,255,255,.015) !important;
  color: var(--text) !important;
  box-shadow: inset 0 1px 0 rgba(255,255,255,.05);
  transition: border-color .2s ease, box-shadow .2s ease, background .2s ease;
}
textarea.form-control{
  min-height: 96px;
  resize: vertical;
}
input[type="color"].form-control{
  min-height: 46px;
  padding: .32rem;
  cursor: pointer;
}
input[type="color"].form-control::-webkit-color-swatch-wrapper{ padding: 0; }
input[type="color"].form-control::-webkit-color-swatch{
  border: 0;
  border-radius: var(--radius-sm);
}
/* Input de archivo (subida de escudo de equipo): botón temático en vez del gris nativo del navegador */
input[type="file"].form-control{
  padding: .5rem .75rem;
  cursor: pointer;
}
input[type="file"].form-control::file-selector-button{
  margin-right: .75rem;
  padding: .4rem .9rem;
  border: 1px solid rgba(252,114,11,.6);
  border-radius: 999px;
  background: rgba(252,114,11,.16);
  color: var(--text);
  font-weight: 600;
  font-size: var(--fs-sm);
  cursor: pointer;
  transition: filter .15s ease, background .15s ease;
}
input[type="file"].form-control::file-selector-button:hover{
  background: rgba(252,114,11,.3);
  filter: brightness(1.08);
}
.form-control::placeholder{
  color: var(--text) !important;
  opacity: .58;
}
.form-control:focus,
.form-select:focus{
  border-color: rgba(252,114,11,.7) !important;
  background: rgba(255,255,255,.075) !important;
  box-shadow: 0 0 0 .25rem rgba(252,114,11,.13), inset 0 1px 0 rgba(255,255,255,.08) !important;
}
/* "Mi Equipo es"/"¿Eres capitán?" (pf_team/pf_captain, compartidos entre "Mi perfil" y el paso
   "Sobre mi" de bienvenida.php): a diferencia del resto del formulario, estos 2 campos SIEMPRE
   son de solo lectura (reflejan la asignación real de equipo, no algo que se edite aquí) — sin
   esto se veían con la misma tipografía blanca "activa" que .form-control usa en todos lados
   (incluido el modo de solo lectura del resto de "Mi perfil", donde SÍ se quiere legible, ver el
   comentario de .kv-value más arriba), así que parecían campos editables en vez de inactivos. */
#pf_team,
#pf_captain{
  color: var(--muted) !important;
  cursor: not-allowed;
}
/* Una sola flecha personalizada para TODOS los selects (elimina la flecha nativa
   del navegador que se dibujaba junto con la de Bootstrap). */
.form-select,
select.search{
  -webkit-appearance: none !important;
  -moz-appearance: none !important;
  appearance: none !important;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath fill='none' stroke='%23ffffff' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3E%3C/svg%3E") !important;
  background-repeat: no-repeat !important;
  background-position: right 1rem center !important;
  background-size: 14px 10px !important;
  padding-right: 2.6rem !important;
}
[data-theme="light"] .form-select,
[data-theme="light"] select.search{
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath fill='none' stroke='%230c1220' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3E%3C/svg%3E") !important;
}
/* Los selects múltiples se ven como listas, no llevan flecha de desplegable */
.form-select[multiple],
select.search[multiple]{
  background-image: none !important;
  padding-right: .75rem !important;
}
.form-select option{
  background: var(--bg-1);
  color: var(--text);
}
select.search{
  padding-top: .65rem;
  padding-bottom: .65rem;
}
.form-check{
  display: flex;
  align-items: center;
  gap: .55rem;
  padding: .7rem .85rem .7rem 2.45rem;
  margin: 0;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: rgba(255,255,255,.03);
}
.form-check-input{
  width: 1.15rem;
  height: 1.15rem;
  margin-top: 0 !important;
  border-color: var(--border) !important;
  background-color: transparent !important;
  box-shadow: none !important;
}
.form-check-input:checked{
  border-color: var(--ads) !important;
  background-color: var(--ads) !important;
}
.form-check-input:focus{ border-color: var(--ads) !important; }
.form-check-label{ color: var(--text); cursor: pointer; }
/* Un .form-check dentro de una tabla (p.ej. el switch "Activa" de una fila) es solo el
   interruptor, no una fila de formulario — sin el borde/fondo/padding de arriba, que ahí se ve
   como una caja rara alrededor del switch. */
/* Bootstrap posiciona el checkbox/switch con float+margin-negativo dentro del padding-left de
   .form-check (pensado para dejarle espacio a una etiqueta a la derecha) — sin etiqueta, como
   aquí, ese float hace que el input no cuente para el ancho del contenedor y el text-align:center
   de la celda no tenga nada que centrar. Se quita el float para que sea un bloque normal dentro
   del <td>, ahí sí centrado de verdad. */
.table .form-check{ border: none; background: transparent; padding: 0; min-height: 0; }
.table .form-check .form-check-input{ float: none; margin-left: 0; }

.modal-content,
.modal-content.bg-dark{
  border: 1px solid var(--border) !important;
  border-radius: var(--radius-xl) !important;
  background: linear-gradient(160deg, rgba(255,255,255,.08), rgba(255,255,255,.025)), var(--bg-0) !important;
  color: var(--text) !important;
  box-shadow: 0 28px 80px rgba(0,0,0,.5);
  overflow: hidden;
}
.modal-header,
.modal-footer{
  border-color: var(--border) !important;
  padding: 1.1rem 1.35rem;
}
.modal-title{ font-weight: 800; letter-spacing: -.01em; }
.modal-body{ padding: 1.35rem; }
.btn-close-white{ filter: invert(1) grayscale(1) brightness(2); }
[data-theme="light"] .btn-close-white{ filter: none; }
[data-theme="light"] .form-control,
[data-theme="light"] .form-select{ background: rgba(255,255,255,.7) !important; }
[data-theme="light"] .modal-content,
[data-theme="light"] .modal-content.bg-dark{ background: linear-gradient(160deg, rgba(255,255,255,.94), rgba(255,255,255,.72)), var(--bg-1) !important; }

/* Las tablas usan los colores de la aplicación aunque Bootstrap incluya table-dark. */
.table.table-dark{
  --bs-table-bg: transparent;
  --bs-table-color: var(--text);
  --bs-table-border-color: var(--border);
  --bs-table-hover-bg: rgba(255,255,255,.07);
  --bs-table-hover-color: var(--text);
  color: var(--text);
  margin-bottom: 0;
}
.table.table-dark > :not(caption) > * > *{
  color: var(--text);
  border-bottom-color: var(--border);
}
.table.table-dark thead th{
  color: var(--muted);
  background: rgba(255,255,255,.045);
  font-size: var(--fs-xs);
  font-weight: 800;
  letter-spacing: .045em;
  text-transform: uppercase;
}
.table-responsive.detail-card{ overflow: hidden; }
[data-theme="light"] .table.table-dark{ --bs-table-hover-bg: rgba(12,18,32,.06); }
[data-theme="light"] .table.table-dark thead th{ background: rgba(12,18,32,.045); }

@media (max-width: 576px){
  .modal-body{ padding: 1rem; }
  .modal-header,
  .modal-footer{ padding: .9rem 1rem; }
}

/* .login-shell ocupa toda la altura visible (nav + contenido); .lock-wrap se reparte el
   espacio restante bajo el nav, así el card queda centrado en el viewport real y no en un
   100vh que se pasaba de largo por sumarse a la altura del nav (se veía descentrado). */
.login-shell{ min-height: 100vh; display:flex; flex-direction:column; overflow-x: hidden; }
.lock-wrap{
  flex:1; min-height:0; display:flex; justify-content:center; padding: 2rem 1rem;
  align-items: center;
  /* "safe center": si la tarjeta no cabe completa en la altura visible (pantallas chicas, ver
     media query de abajo), se alinea arriba en vez de centrarse — centrar algo más alto que el
     viewport lo desborda por IGUAL arriba y abajo, así que la parte de abajo (el botón de "¿Necesitas
     ayuda?") quedaba fuera de la pantalla y se sentía "cortada" en vez de solo necesitar scroll.
     Navegadores que no soportan "safe" ignoran esta línea y se quedan con el center de arriba. */
  align-items: safe center;
}
.lock-card{
  width: 100%; max-width: 520px;
  border-radius: var(--radius-xl);
  border: 1px solid var(--border);
  background: rgba(255,255,255,.04);
  box-shadow: var(--shadow);
  overflow:hidden;
}
[data-theme="light"] .lock-card{ background: var(--card); }
.lock-head{ padding: 1.35rem 1.35rem 0.9rem; }
/* Logo en forma de avatar circular, arriba de "¡Bienvenido!" en el login — el ANILLO (borde) es
   el degradado con los mismos dos colores de siempre para Adolescentes/Jóvenes (--ads/--joven,
   ver .account-avatar), aunque esta pantalla es previa a iniciar sesión y no tiene grupo propio;
   el fondo detrás del logo (que es un PNG transparente, no un círculo relleno) es oscuro fijo, no
   parte del degradado. */
.login-logo-avatar{
  width: 84px;
  height: 84px;
  margin: 0 auto .9rem;
  border-radius: 50%;
  padding: 3px;
  background: linear-gradient(135deg, var(--ads), var(--joven));
}
.login-logo-avatar img{
  display: block;
  box-sizing: border-box;
  width: 100%;
  height: 100%;
  padding: .9rem;
  border-radius: 50%;
  background: var(--card);
  object-fit: contain;
}
.lock-body{ padding: 0 1.35rem 1.35rem; }
@media (max-width: 480px){
  /* Menos aire arriba/abajo y un logo más chico en pantallas bajas — para que la tarjeta completa
     quepa sin scroll en la mayoría de los celulares en vez de depender solo del "safe center".
     Va DESPUÉS de las reglas base de arriba (mismo selector, misma especificidad): un @media con
     igual especificidad que aparece ANTES en el archivo pierde contra la regla base que viene
     después, sin importar el ancho de pantalla — por eso este bloque no estaba haciendo nada. */
  .lock-wrap{ padding: 1rem .75rem; }
  .lock-head{ padding: 1rem 1.1rem .6rem; }
  .lock-body{ padding: 0 1.1rem 1.1rem; }
  .login-logo-avatar{ width: 64px; height: 64px; margin-bottom: .6rem; }
}
.lock-input{
  border: 1px solid var(--border);
  background: rgba(255,255,255,.03);
  color: var(--text);
  border-radius: var(--radius-md);
  padding: .85rem 1rem;
}
.lock-input:focus{
  outline:none;
  box-shadow: 0 0 0 .25rem rgba(244,44,44,.14);
  border-color: rgba(244,44,44,.32);
}
/* Grupo de input con icono (usuario/contraseña) y botón de mostrar/ocultar contraseña,
   con la misma paleta roja/"lock" del resto del card en vez de los grises de Bootstrap. */
.lock-input.form-control{
  border: 1px solid var(--border) !important;
  background: rgba(255,255,255,.03) !important;
  color: var(--text) !important;
  padding: .85rem 1rem !important;
  box-shadow: none !important;
}
.lock-input.form-control:focus{
  outline: none;
  box-shadow: 0 0 0 .25rem rgba(244,44,44,.14) !important;
  border-color: rgba(244,44,44,.32) !important;
  z-index: 3;
}
.lock-eye-btn{
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: rgba(255,255,255,.05);
  color: var(--muted);
  padding: .85rem 1.05rem;
}
.lock-eye-btn:hover{ color: var(--text); }
[data-theme="light"] .lock-eye-btn{ background: rgba(255,255,255,.7); }
/* input-group-text unificado en TODA la app (antes usaba el gris/radio por defecto de
   Bootstrap, distinto al resto de inputs): mismo radio y paleta oscura que .form-control, con
   padding izq/der un poco mayor para que un icono o una sola letra ($) se vea con espacio
   simétrico arriba/lados (aspecto "cuadrado") en vez de angosto. */
.input-group-text{
  border: 1px solid var(--border) !important;
  background: rgba(255,255,255,.05) !important;
  color: var(--muted) !important;
  border-radius: var(--radius-md) !important;
  padding: .85rem 1.05rem !important;
  min-height: 46px;
  box-sizing: border-box;
}
[data-theme="light"] .input-group-text{ background: rgba(255,255,255,.7) !important; }
/* Radios internos a 0 donde el input-group une controles, para que el grupo completo (icono +
   input + botón, en cualquier orden/cantidad) se vea como una sola pieza uniforme. */
.input-group > :not(:first-child){
  margin-left: -1px;
  border-top-left-radius: 0 !important;
  border-bottom-left-radius: 0 !important;
}
.input-group > :not(:last-child){
  border-top-right-radius: 0 !important;
  border-bottom-right-radius: 0 !important;
}
/* Botones (−)/(+) de los inputs numéricos (ver initNumberSteppers() en app.js) — mismo look que
   .input-group-text (que ya se ve como una sola pieza junto al input gracias a las reglas de
   arriba), pero clickeables. */
.qty-stepper-btn{
  border: 1px solid var(--border);
  background: rgba(255,255,255,.05);
  color: var(--muted);
  padding: .85rem 1rem;
  min-height: 46px;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background .15s ease, color .15s ease;
}
.qty-stepper-btn:hover{ background: rgba(255,255,255,.12); color: var(--text); }
[data-theme="light"] .qty-stepper-btn{ background: rgba(255,255,255,.7); }
.qty-stepper input[type="number"]{ text-align: center; }
/* Icono grande centrado para feedback en modales (ej. error de acceso); el color lo da la
   clase badge-neon-* que se combine (badge-neon-red, etc.), esta solo define tamaño/forma. */
.feedback-icon{
  width: 64px;
  height: 64px;
  border-radius: 50% !important;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.8rem;
  margin: 0 auto;
  padding: 0 !important;
}

/* Wizard del modal de discípulo (Añadir/Editar): stepper numerado arriba + contenedor de alto
   fijo para el contenido del paso, así cambiar de paso nunca cambia el tamaño del modal. */
.pf-stepper{
  display: flex;
  flex-wrap: wrap;
  gap: .5rem;
  margin-bottom: 1rem;
}
.pf-step{
  display: flex;
  align-items: center;
  gap: .5rem;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: rgba(252, 114, 11, .10);
  color: var(--muted);
  padding: .35rem .9rem .35rem .35rem;
  font-weight: 600;
  font-size: var(--fs-sm);
  cursor: pointer;
  transition: filter .15s ease, background .15s ease, border-color .15s ease, color .15s ease;
}
.pf-step:hover{ filter: brightness(1.2); }
.pf-step-num{
  width: 24px; height: 24px;
  border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: rgba(255,255,255,.08);
  font-size: var(--fs-xs);
  font-weight: 800;
  flex-shrink: 0;
}
.pf-step.active{
  background: linear-gradient(135deg, rgba(252,114,11,.22), rgba(244,44,44,.18));
  border-color: rgba(252,114,11,.6);
  color: var(--text);
}
.pf-step.active .pf-step-num{ background: var(--ads); color: var(--text); }
/* Encabezado compacto de sub-sección dentro de un paso (más angosto que un h4, para caber sin scroll). */
.pf-subhead{
  font-size: var(--fs-md);
  font-weight: 800;
  letter-spacing: .04em;
  color: var(--atext);
  padding-top: 1rem;
}
.pf-steps-body{
  height: auto;
  overflow: visible;
  padding-right: .35rem;
}
/* Modal grande de wizard (crear/editar discípulo en admin, corroborar datos del primer login): el
   stepper/tabs ya NO va fijo (sin position:sticky ni fondo propio) — se desplaza junto con el
   contenido del paso, como el resto del modal. El modal se deja crecer con su contenido (tope de
   90vh solo para que no se desborde en monitores chicos, con scroll de Bootstrap/.modal-dialog-
   scrollable) para que .modal-dialog (más abajo en este mismo archivo, centra todos los modales)
   pueda centrarlo de verdad en vez de forzarlo a una caja fija de 80vh. */
.modal-dialog.modal-wizard-fixed{ max-height: 90vh; }

/* Modal de crear/editar discípulo (admin): más grande que el resto de modales (min-width al 80%
   de la pantalla, alto fijo al 80%). El alto se aplica en .modal-content, NO en .modal-dialog, a
   propósito: .modal-dialog es la caja invisible que .modal-dialog (regla de arriba, "centra todos
   los modales") deja casi a pantalla completa para que align-items:center reparta el espacio
   sobrante arriba/abajo por igual. Si aquí también le pusiéramos una altura (como hacía
   .modal-wizard-fixed con max-height), esa caja invisible se encogería solo por abajo (su
   margin-top fijo de Bootstrap no se toca), rompiendo la simetría y dejando el modal pegado
   arriba. Por eso el .modal-dialog de #personModal se libera de ese tope (max-height:none anula el
   90vh de .modal-wizard-fixed) y el alto real se mueve a la tarjeta visible (.modal-content, que sí
   acepta unidades vh sin depender de la altura del padre): height fija (no min/max) para que sea
   siempre 80vh exacto, con scroll interno (.modal-body) tanto si el contenido del paso es más
   corto como si es más largo que eso. */
#personModal .modal-dialog{ min-width: 80vw; max-height: none; }
#personModal .modal-content{ height: 80vh; }

.card-person,
.card-person a,
.card-person a:hover,
.card-person a:focus,
.card-person a:visited {
  text-decoration: none;
  color: inherit;
}

/* Fondo fijo + animado (no se corta) */
body {
  background: var(--bg-0); /* fallback */
  position: relative;
}

/* Capa base fija */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -2;
  pointer-events: none;

  background:
    radial-gradient(1200px 800px at 20% -10%, rgba(252,114,11,.22), transparent 55%),
    radial-gradient(900px 700px at 95% 20%, rgba(244,44,44,.18), transparent 55%),
    linear-gradient(180deg, var(--bg-0), var(--bg-1));
}

/* Destellos animados (capa superior) */
body::after {
  content: "";
  position: fixed;
  inset: -20%;
  z-index: -1;
  pointer-events: none;

  background:
    radial-gradient(260px 260px at 30% 25%, rgba(252,114,11,.45), transparent 60%),
    radial-gradient(340px 340px at 70% 60%, rgba(244,44,44,.35), transparent 62%),
    radial-gradient(220px 220px at 80% 20%, rgba(255,255,255,.08), transparent 60%);
  filter: blur(24px);
  opacity: .95;

  animation: auroraMove 14s ease-in-out infinite alternate;
  will-change: transform;
}

/* Animación suave “con vida” */
@keyframes auroraMove {
  0%   { transform: translate3d(-2%, -1%, 0) scale(1.02) rotate(-1deg); }
  35%  { transform: translate3d(2%, 1%, 0)  scale(1.06) rotate(1deg); }
  70%  { transform: translate3d(-1%, 2%, 0) scale(1.03) rotate(0deg); }
  100% { transform: translate3d(3%, -2%, 0) scale(1.08) rotate(2deg); }
}

/* Accesibilidad: respeta usuarios sin animación */
@media (prefers-reduced-motion: reduce) {
  body::after { animation: none; }
}
/* Pills neon clickeables para nombres */
.badge-soft--2 {
  display: inline-flex;
  align-items: center;
  gap: .35rem;
  padding: .35rem .65rem;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: rgba(255,255,255,.04);
  color: var(--text);
  text-decoration: none;
  transition: transform .18s ease, filter .18s ease, border-color .18s ease;
  margin: 0 .35rem .35rem 0;
  font-weight: 600;
  font-size: var(--fs-sm);
}

.badge-soft--2:hover {
  filter: brightness(1.1);
  border-color: rgba(,,,1);
}

/* por grupo de la persona enlazada */
.badge-soft--ads {
  border-color: rgba(252,114,11,.75);
  box-shadow: 0 10px 30px rgba(252,114,11,.30);
  background: rgba(252,114,11,.35);
}

.badge-soft--joven {
  border-color: rgba(244,44,44,.75);
  box-shadow: 0 10px 30px rgba(244,44,44,.30);
  background: rgba(244,44,44,.35);
}

/* cuando NO existe en el directorio */
.badge-soft--disabled {
  opacity: 1;
  cursor: default;
  border-color: var(--border);
  box-shadow: none;
}

.card-person{
  position: relative;
  background: linear-gradient(180deg, rgba(255,255,255,.04), rgba(255,255,255,.01));
  border: 1px solid var(--border);
  border-radius: var(--radius);
  /* overflow:visible (antes hidden) a propósito: el halo (::before) y el borde eléctrico (::after,
     más abajo) necesitan poder asomarse FUERA de la tarjeta para verse como en el ejemplo — el
     recorte a esquinas redondeadas de la foto/badges/etc. ahora lo hace .card-person-inner (nuevo
     wrapper de todo el contenido visual, ver renderGrid()/renderTeamsGrid() en app.js), no
     .card-person mismo. */
  overflow: visible;
  box-shadow: var(--shadow);
  transform: translateY(0);
  transition: transform .25s ease, box-shadow .25s ease, border-color .25s ease;
}
.card-person-inner{
  position: relative;
  height: 100%;
  border-radius: inherit;
  overflow: hidden;
  /* El bug real detrás del crash en iOS al hacer scroll en Directorio (y no en Equipos): el
     desvanecido de .avatar (mask-image, ver más abajo) se aplicaba SIEMPRE a las 70+ imágenes de
     las 41 tarjetas del Directorio completo (Equipos, al partir por equipo, nunca pasa de ~16-20) —
     a diferencia del borde eléctrico (que sí se gatea a la tarjeta activa), este mask corría sin
     condición en TODAS a la vez, y mantener compuestas esa cantidad de capas enmascaradas es lo que
     agotaba memoria en iOS. content-visibility:auto le dice al navegador que deje de pintar/componer
     el contenido de las tarjetas que están fuera (o lejos) del viewport —incluyendo su máscara— sin
     tocar el anillo eléctrico (vive en .card-person::after, un nivel arriba, fuera de este wrapper).
     contain-intrinsic-size con "auto" recuerda el alto real ya medido de cada tarjeta la primera vez
     que se pintó, así que ocultarla después no le hace perder su espacio en la cuadrícula (sin esto,
     content-visibility puede colapsar su alto a 0 mientras está fuera de pantalla). */
  content-visibility: auto;
  contain-intrinsic-size: auto 520px;
}
/* Borde del color del grupo (Adolescentes/Jóvenes) al que pertenece la persona, solo al hacer
   hover sobre la tarjeta — en reposo se queda con el borde neutro de siempre. Toma el color real
   del catálogo (--group-color, ver groupColorVar()) puesto en el propio .card-person, y cae al
   naranja/rojo fijo del sistema mientras ese color todavía no se cargó. */
.card-person.card-group-ads:hover{ border-color: var(--group-color, var(--ads)); }
.card-person.card-group-joven:hover{ border-color: var(--group-color, var(--joven)); }

/* halo de luz — del color del grupo de ESA persona (--group-color, puesto por groupColorVar() en
   renderGrid()) en vez de un degradado ads/joven fijo igual para todos. */
.card-person::before{
  content: "";
  position: absolute;
  inset: -2px;
  border-radius: inherit;
  background: linear-gradient(
    120deg,
    var(--group-color, var(--ads)),
    var(--group-color, var(--joven)),
    var(--group-color, var(--ads))
  );
  opacity: 0;
  filter: blur(10px);
  transition: opacity .25s ease;
  z-index: 0;
}

/* contenido encima del glow */
.card-person > *{
  position: relative;
  z-index: 1;
}

.card-person:hover{
  transform: translateY(-6px);
  box-shadow: 0 24px 70px rgba(0,0,0,.55);
}

/* aparece el glow */
.card-person:hover::before{
  opacity: .05;
}

/* "Borde eléctrico" de los grids de Directorio y Equipos (inspirado en
   https://codepen.io/BalintFerenczy/full/yyYErXa, recoloreado con el color de CADA grid en vez del
   naranja fijo del pen — el del grupo de la persona en Directorio, el del equipo en Equipos, vía
   --electric-color de abajo): un anillo de degradado cónico recortado a solo el grosor del borde
   (misma técnica mask+mask-composite:xor que un borde-degradado sin relleno), distorsionado con el
   ruido de #electric-jitter (definido una sola vez por página — directorio.php/teams.php — y
   reusado por todas sus tarjetas) para que tiemble como una chispa en vez de girar liso. Pausado en
   reposo; corre con :hover (escritorio) o con .is-active (móvil/touch, la misma clase que activa
   initCardPhotoSwapObserver() al cruzar el centro de la pantalla durante el scroll — sin hover real
   no hay otra forma de "activar" la tarjeta que sí está viendo el usuario). Con ~40 tarjetas en el
   grid, animar SOLO la activa (en vez de las 40 a la vez) es lo que hace que esto siga siendo
   barato. */
@property --electric-angle{ syntax:"<angle>"; initial-value:0deg; inherits:false; }
#grid .card-person{ --electric-color: var(--group-color, var(--ads)); }
#teamsGrid .card-person{ --electric-color: var(--team-color, var(--ads)); }
#grid .card-person::after,
#teamsGrid .card-person::after{
  /* inset negativo (se asoma FUERA de la tarjeta, como en el ejemplo) — ya es seguro desde que
     .card-person pasó a overflow:visible y el recorte de esquinas redondeadas de la foto/badges lo
     hace .card-person-inner (ver arriba).
     A PROPÓSITO sin background/mask/animation aquí: con ~40-80 tarjetas en el grid, asignarle a
     TODAS un conic-gradient + mask-composite:xor (aunque esté en opacity:0) obliga a iOS a mantener
     compuesta esa capa enmascarada en cada una todo el tiempo, no solo cuando se ve — eso solo era
     "lento"/con jank en desktop, pero en iOS alcanza para agotar memoria y matar la pestaña
     ("Aw, Snap!") apenas .is-active prende 2-3 tarjetas durante el scroll (confirmado: coincide con
     el reporte de que se traba justo en la segunda tarjeta). Todo el costo real (gradiente, máscara,
     animación) se movió a la regla de abajo (:hover/.is-active), así una tarjeta en reposo es un
     pseudo-elemento vacío, prácticamente gratis de pintar. */
  content: "";
  position: absolute;
  inset: -4px;
  opacity: 0;
  z-index: 0;
  pointer-events: none;
  transition: opacity .25s ease;
}
#grid .card-person:is(:hover,.is-active)::after,
#teamsGrid .card-person:is(:hover,.is-active)::after{
  padding: 2px;
  border-radius: inherit;
  background: conic-gradient(from var(--electric-angle),
    transparent, var(--electric-color), #fff,
    var(--electric-color), transparent);
  -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
  -webkit-mask-composite: xor;
          mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
          mask-composite: exclude;
  opacity: .85;
  animation: electric-spin 2.4s linear infinite;
}
/* El filtro de ruido (#electric-jitter, con <animate> de SMIL regenerando la textura de ruido
   varias veces por segundo) SOLO se activa en dispositivos con hover real — es decir, nunca por
   scroll en móvil. En un mouse, ese temblor solo corre para la UNA tarjeta bajo el cursor; en
   scroll táctil, .is-active puede prender/apagar el filtro en tarjeta tras tarjeta muy seguido
   mientras el usuario se desplaza rápido, y regenerar ruido SVG a ese ritmo es lo bastante pesado
   como para trabar el scroll y, tras un rato, hacer que el navegador mate la pestaña. En móvil el
   anillo se sigue viendo y girando (barato, es solo un conic-gradient animado), nada más sin el
   temblor. */
@media (hover: hover){
  #grid .card-person:is(:hover,.is-active)::after,
  #teamsGrid .card-person:is(:hover,.is-active)::after{ filter: url(#electric-jitter); }
}
@keyframes electric-spin{ to{ --electric-angle: 360deg; } }
@media (prefers-reduced-motion: reduce){
  #grid .card-person:is(:hover,.is-active)::after,
  #teamsGrid .card-person:is(:hover,.is-active)::after{ animation: none; }
}

/* Mismo "borde eléctrico" en la tarjeta de la foto de perfil del detalle de un discípulo
   (detail.html) — a diferencia del grid (docenas de tarjetas, de ahí que ahí el anillo solo se
   active en la tarjeta visible para no acumular capas compuestas), aquí es UNA sola tarjeta fija en
   toda la página, así que el anillo va siempre activo, sin gatear a :hover/.is-active. */
.detail-photo-card{ position: relative; }
.detail-photo-card::after{
  content: "";
  position: absolute;
  inset: -4px;
  padding: 2px;
  border-radius: inherit;
  background: conic-gradient(from var(--electric-angle),
    transparent, var(--electric-color, var(--group-color, var(--ads))), #fff,
    var(--electric-color, var(--group-color, var(--ads))), transparent);
  -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
  -webkit-mask-composite: xor;
          mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
          mask-composite: exclude;
  opacity: .85;
  z-index: 0;
  pointer-events: none;
  animation: electric-spin 2.4s linear infinite;
}
@media (hover: hover){
  .detail-photo-card::after{ filter: url(#electric-jitter); }
}
@media (prefers-reduced-motion: reduce){
  .detail-photo-card::after{ animation: none; }
}

/* Chips genéricos */
.badges{
  display: flex;
  flex-wrap: wrap;
  gap: .5rem;
}

.badge{
  display: inline-flex;
  align-items: center;
  gap: .35rem !important;
  padding: .15rem .5rem !important;
  border-radius: 999px !important;
  font-weight: 200;
  letter-spacing: .2px;
  color: var(--text) !important;

  background: rgba(255,255,255,.08);
  border: 1px solid var(--border);

  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
}

/* Variantes por deporte (opcional) */
.chip-futbol{
  border-color: green;
  box-shadow: 0 0 0 .15rem rgba(252,114,11,.15);
}

.chip-basquet{
  border-color: orange;
  box-shadow: 0 0 0 .15rem rgba(53,186,116,.15);
}

.chip-beisbol{
  border-color: blue;
  box-shadow: 0 0 0 .15rem rgba(120,160,255,.15);
}

/* Vitrina de premios (tab "Vitrina" del detalle del discípulo): emblemas con el ícono del premio,
   nombre y año, con el mismo dorado neón que .badge-neon-gold/.btn-neon-yellow (medalla/trofeo). */
.award-badges-grid{
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
}
.award-badge{
  display: flex;
  flex-direction: column;
  align-items: center;
  width: 125px;
  padding: .9rem .5rem .7rem;
  border-radius: var(--radius-lg);
  background: rgba(234,179,8,.08);
  border: 1px solid rgba(234,179,8,.5);
  box-shadow: 0 0 12px rgba(234,179,8,.18);
  text-align: center;
  cursor: pointer;
  transition: transform .15s ease, border-color .15s ease, box-shadow .15s ease, filter .15s ease;
}
.award-badge:hover{
  transform: translateY(-3px);
  border-color: #facc15;
  box-shadow: 0 0 20px rgba(234,179,8,.4), 0 12px 28px rgba(234,179,8,.22);
  filter: brightness(1.05);
}
.award-badge:focus-visible{ outline: 2px solid #facc15; outline-offset: 2px; }
.award-badge-icon{
  width: 56px;
  height: 56px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.7rem;
  margin-bottom: .5rem;
  background: linear-gradient(135deg, rgba(234,179,8,.28), rgba(250,204,21,.12));
  border: 1px solid rgba(234,179,8,.6);
}
.award-badge-name{
  font-size: var(--fs-xs);
  font-weight: 700;
  line-height: 1.2;
  color: #facc15;
}
.award-badge-year{
  font-size: var(--fs-xs);
  color: var(--muted);
  margin-top: .2rem;
}

/* "Cumpleaños próximos" y "Posiciones del torneo" (Inicio, panel admin): mismo look de emblema
   que .award-badge de la Vitrina de premios, pero con su propio grid (4 columnas fijas — máximo
   4 emblemas visibles por fila, en vez del flex-wrap de ancho variable de .award-badges-grid) y
   una foto real (cumpleaños) o el escudo del equipo (posiciones) en el círculo en vez de un
   ícono de emoji. */
.birthday-badges-grid, .position-badges-grid{
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 1rem;
}
.birthday-badge, .position-badge{
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: .9rem .5rem .7rem;
  border-radius: var(--radius-lg);
  text-align: center;
  cursor: default;
  transition: transform .15s ease, border-color .15s ease, box-shadow .15s ease, filter .15s ease;
}
.birthday-badge{
  background: rgba(244,114,182,.08);
  border: 1px solid rgba(244,114,182,.5);
  box-shadow: 0 0 12px rgba(244,114,182,.18);
}
.birthday-badge:hover{
  transform: translateY(-3px);
  border-color: #f9a8d4;
  box-shadow: 0 0 20px rgba(244,114,182,.4), 0 12px 28px rgba(244,114,182,.22);
  filter: brightness(1.05);
}
.birthday-badge-day{
  font-size: var(--fs-xs);
  font-weight: 700;
  color: #f9a8d4;
  margin-bottom: .5rem;
}
/* Envoltura del avatar en position:relative para poder sobreponer el pastel 🎂 en su borde
   inferior (position:absolute + translate), en vez de ir en línea junto al texto de la edad. */
.birthday-badge-avatar-wrap{
  position: relative;
  margin-bottom: .5rem;
}
.birthday-badge-avatar{
  display: block;
  width: 56px; height: 56px;
  border-radius: 50%;
  object-fit: cover;
  border: 1px solid rgba(244,114,182,.6);
}
.birthday-badge-cake{
  position: absolute;
  bottom: 0;
  left: 50%;
  transform: translate(-50%, 35%);
  font-size: .65rem;
  padding: 10px;
  line-height: 1;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(244, 114, 182, .55);
  border: 1px solid rgba(244, 114, 182, .7);
}
.birthday-badge-name{
  font-size: var(--fs-sm);
  font-weight: 700;
  line-height: 1.2;
  margin-top: 10px;
}
.birthday-badge-age{
  font-size: var(--fs-xs);
  color: var(--muted);
  margin-top: .2rem;
}
/* Posiciones del torneo: color del emblema según el lugar — oro/plata/bronce para el podio,
   azul neutro para el resto — mismos tonos que .badge-neon-gold/silver/bronze/blue. */
.position-badge-gold{
  background: rgba(234,179,8,.08);
  border: 1px solid rgba(234,179,8,.5);
  box-shadow: 0 0 12px rgba(234,179,8,.18);
}
.position-badge-gold:hover{ border-color: #facc15; box-shadow: 0 0 20px rgba(234,179,8,.4), 0 12px 28px rgba(234,179,8,.22); }
.position-badge-gold .position-badge-rank{ color: #facc15; }
.position-badge-silver{
  background: rgba(203,213,225,.08);
  border: 1px solid rgba(203,213,225,.5);
  box-shadow: 0 0 12px rgba(203,213,225,.18);
}
.position-badge-silver:hover{ border-color: #e2e8f0; box-shadow: 0 0 20px rgba(203,213,225,.4), 0 12px 28px rgba(203,213,225,.22); }
.position-badge-silver .position-badge-rank{ color: #e2e8f0; }
.position-badge-bronze{
  background: rgba(217,119,6,.08);
  border: 1px solid rgba(217,119,6,.5);
  box-shadow: 0 0 12px rgba(217,119,6,.18);
}
.position-badge-bronze:hover{ border-color: #fb923c; box-shadow: 0 0 20px rgba(217,119,6,.4), 0 12px 28px rgba(217,119,6,.22); }
.position-badge-bronze .position-badge-rank{ color: #fb923c; }
.position-badge-other{
  background: rgba(59,130,246,.08);
  border: 1px solid rgba(59,130,246,.5);
  box-shadow: 0 0 12px rgba(59,130,246,.18);
}
.position-badge-other:hover{ border-color: #60a5fa; box-shadow: 0 0 20px rgba(59,130,246,.4), 0 12px 28px rgba(59,130,246,.22); }
.position-badge-other .position-badge-rank{ color: #60a5fa; }
.position-badge:hover{
  transform: translateY(-3px);
  filter: brightness(1.05);
}
.position-badge-rank{
  font-size: var(--fs-xs);
  font-weight: 800;
  margin-bottom: .5rem;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: .35rem;
}
/* Texto "Primer lugar"/"Segundo lugar"/etc junto al ícono de medalla — visible en todos los
   anchos de pantalla, no solo en mobile (ver rankLabel() en app.js). */
.position-badge-rank-label{
  font-weight: 700;
  text-transform: none;
}
.position-badge-shield{
  margin-bottom: .5rem;
}
.position-badge-name{
  font-size: var(--fs-sm);
  font-weight: 700;
  line-height: 1.2;
  margin-top: 10px;
}
.position-badge-points{
  font-size: var(--fs-xs);
  color: var(--muted);
  margin-top: .2rem;
}
@media (max-width: 576px){
  .birthday-badges-grid{ grid-template-columns: repeat(2, 1fr); }
  /* Posiciones del torneo en mobile: una tarjeta por fila (col-12) en vez de 2 columnas, con un
     acomodo distinto al de escritorio — lugar arriba a todo lo ancho, luego una fila con el
     escudo a la izquierda y, a la derecha, el nombre del equipo con los puntos apilados justo
     debajo (no debajo del escudo). */
  .position-badges-grid{ grid-template-columns: 1fr; }
  .position-badge{
    display: grid;
    grid-template-columns: auto 1fr;
    grid-template-areas:
      "rank   rank"
      "shield name"
      "shield points";
    align-items: center;
    text-align: left;
    padding: .9rem 1rem;
  }
  .position-badge-rank{
    grid-area: rank;
    justify-content: flex-start;
    margin-bottom: .6rem;
  }
  .position-badge-shield{
    grid-area: shield;
    justify-self: start;
    align-self: center;
    margin-bottom: 0;
    margin-right: .9rem;
  }
  .position-badge-name{
    grid-area: name;
    justify-self: start;
    align-self: end;
    margin-top: 0;
    font-size: var(--fs-lg);
  }
  .position-badge-points{
    grid-area: points;
    justify-self: start;
    align-self: start;
    margin-top: .2rem;
  }
}

.truncate-name {
  display: block;
  max-width: 100%;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.card-divider{
  width: 100%;
  height: 1px;
  opacity:.6;
  background-color: var(--border);
  margin-bottom: 5px;
  margin-top: 10px;
}

/* Mini resumen de la tarjeta del directorio (ver personSummary() en app.js): recortado a 2
   renglones como máximo sin importar qué tan largo venga el texto armado. */
.card-summary{
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  color: var(--text);
  min-height: 2.6em;
}
/* Fila de badges al pie de la tarjeta (género, escolaridad, equipo): alineados en el mismo
   renglón vertical sin importar que mezclen badges de solo-emoji con el de equipo (ícono+texto). */
.card-badges-row{
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: .4rem;
}
/* Badges de género/escolaridad de la tarjeta: solo llevan un emoji, así que necesitan menos
   padding horizontal que un badge de texto normal para no verse huecos. */
/* Badges cuyo contenido es solo un ícono/emoji/letra/número (género, escolaridad, iniciales de
   grupo, medallas de podio…): van completamente redondos en vez de píldora, para distinguirlos
   visualmente de los badges de texto (grupo, equipo). */
.badge-round,
.badge-emoji{
  display: inline-flex !important;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  padding: 0 !important;
  border-radius: 50% !important;
  font-size: .92rem;
  line-height: 1;
}

/* Tarjetas de "Filtros" en acordeón: colapsadas por default en todo el sistema, para mantener las
   vistas más limpias — el título/subtítulo y el botón de colapsar quedan a la misma altura. */
.filter-card-header{
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: .75rem;
  cursor: pointer;
  user-select: none;
}
.filter-collapse-icon{
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  background: rgba(255,255,255,.06);
  border: 1px solid var(--border);
  font-size: 1.05rem;
  color: var(--text);
  transition: transform .2s ease, background .15s ease;
  flex-shrink: 0;
}
.filter-card-header:hover .filter-collapse-icon{ background: rgba(255,255,255,.12); }
.filter-card-header[aria-expanded="true"] .filter-collapse-icon{ transform: rotate(180deg); }

.captain-badge{
  margin-left:4px;
  cursor:help;
  font-size:.95em;
}

/* Grupo Sí/No excluyente (radio con apariencia de pastilla) para las preguntas de la encuesta
   que solo admiten una respuesta booleana ("No" por default al crear). Envuelto en un cajón con
   el mismo look&feel que .form-control para que se vea consistente con el resto de inputs. */
.pf-yesno{
  display:flex; align-items:center; gap:.4rem; flex-wrap:wrap;
  border:1px solid var(--border); border-radius:var(--radius-md);
  background:rgba(255,255,255,.0); min-height:46px; padding:.4rem .5rem;
}
.pf-yesno input{ position:absolute; opacity:0; width:0; height:0; pointer-events:none; }
.pf-yesno label{
  cursor:pointer; padding:.35rem .8rem; border-radius:999px; border:1px solid var(--border);
  font-size:var(--fs-sm); font-weight:700; color:var(--muted); background:rgba(255,255,255,.05);
  transition:filter .15s ease,background .15s ease,border-color .15s ease,color .15s ease; user-select:none;
}
.pf-yesno label:hover{ filter:brightness(1.15); }
/* Respuesta de solo lectura (p.ej. bienvenida.php: "¿Asistes regularmente...?"/"¿Es la primera
   vez...?", ya no editables desde la introducción) — se nota que no se puede tocar en vez de
   verse clickeable igual que las demás. */
.pf-yesno input:disabled + label{ cursor:not-allowed; opacity:.55; }
.pf-yesno input:disabled + label:hover{ filter:none; }
.pf-yesno input:focus-visible + label{ outline:2px solid var(--ads); outline-offset:2px; }
.pf-yesno input:checked + label.pf-yes{ background:rgba(34,197,94,.25); border-color:rgba(34,197,94,.75); color:var(--text); }
.pf-yesno input:checked + label.pf-no{ background:rgba(244,44,44,.2); border-color:rgba(244,44,44,.7); color:var(--text); }
/* Tercera pastilla neutral ("No recuerdo exactamente", etc.) para preguntas de la encuesta que
   no son estrictamente Sí/No: mismo trato visual que .pf-yes/.pf-no pero en ámbar, sin implicar
   una respuesta afirmativa ni negativa. */
.pf-yesno input:checked + label.pf-maybe{ background:rgba(245,158,11,.22); border-color:rgba(245,158,11,.7); color:var(--text); }

/* Lista de opciones fijas en pastillas horizontales (checkbox=multi, radio=single): redes
   sociales, deportes, actividades de fe, momentos/frecuencia de oración, personas por las que se ora.
   Mismo cajón que .pf-yesno para que combine con el resto de inputs del formulario. */
.pf-checklist{
  display:flex; flex-wrap:wrap; gap:.4rem; align-items:center;
  border:1px solid transparent; border-radius:var(--radius-md);
  background:rgba(255,255,255,0); min-height:46px; padding:.5rem 0rem;
}
/* La pastilla (borde+relleno+radio) vive en el span que envuelve input+ícono+texto, no en el
   label, para que el cuadrado del checkbox y el ícono se vean DENTRO del borde de la opción y
   antes del texto, en vez de sueltos por fuera. Todo hereda color de aquí (ícono y texto). */
.pf-checklist > span{
  display:inline-flex; align-items:center; gap:.35rem; cursor:pointer; user-select:none;
  padding:.3rem .8rem; border-radius:999px; border:1px solid var(--border);
  font-size:var(--fs-sm); font-weight:600; color:var(--muted); background:rgba(255,255,255,.05);
  transition:filter .15s ease,background .15s ease,border-color .15s ease,color .15s ease; min-height: 46px;
}
.pf-checklist > span:hover{ filter:brightness(1.15); }
.pf-checklist input[type="radio"]{ position:absolute; opacity:0; width:0; height:0; pointer-events:none; }
/* Checkbox visible (no oculto como el radio) para que a simple vista se note que son varias
   opciones elegibles a la vez, con el mismo estilo que .form-check-input del resto de la app. */
.pf-checklist input[type="checkbox"]{
  appearance:none; -webkit-appearance:none; margin:0; flex-shrink:0;
  width:16px; height:16px; border-radius:4px;
  border:1px solid var(--border) !important; background-color:transparent !important;
  cursor:pointer; transition:background-color .15s ease,border-color .15s ease;
}
.pf-checklist input[type="checkbox"]:checked{
  border-color:var(--ads) !important; background-color:var(--ads) !important;
  background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10l3 3l6-6'/%3e%3c/svg%3e");
  background-repeat:no-repeat; background-position:center; background-size:11px;
}
.pf-checklist input[type="checkbox"]:focus-visible{ outline:2px solid var(--ads); outline-offset:2px; }
.pf-check-icon{ font-size:.95em; line-height:1; }
.pf-checklist label{ cursor:pointer; user-select:none; }
/* El radio sigue oculto (sin cuadrado), así que su foco se marca en la pastilla completa; el
   checkbox ya trae su propio outline arriba. El fondo/borde de "elegido" también va en la
   pastilla completa (:has), para que cubra ícono + texto sin depender de un combinador "+". */
.pf-checklist > span:has(input[type="radio"]:focus-visible){ outline:2px solid var(--ads); outline-offset:2px; }
.pf-checklist > span:has(input:checked){ background:rgba(252,114,11,.22); border-color:rgba(252,114,11,.7); color:var(--text); }
.pf-checklist-other-input{ min-width:180px; flex:1 1 180px; }

/* Tema oscuro para Tom Select (comboboxes de búsqueda + chips + crear opción). Solo se
   neutraliza el WRAPPER exterior (.ts-wrapper.form-control, si Tom Select le copia la clase);
   .ts-control es la caja realmente visible y es la que debe verse igual que .form-control —
   antes .ts-wrapper > .ts-control (misma especificidad que .ts-wrapper.form-control, más alta
   que .ts-control solo) le ganaba a estas reglas y por eso no se veía la caja. */
.ts-wrapper.form-control{
  padding:0 !important; height:auto !important; min-height:0 !important;
  background:transparent !important; border:0 !important; box-shadow:none !important;
}
.ts-control{
  display:flex !important; flex-wrap:wrap !important; align-items:center !important;
  border:1px solid var(--border) !important;
  border-radius:var(--radius-md) !important;
  background:rgba(255,255,255,.085) !important;
  color:var(--text) !important;
  min-height:46px;
  padding:.4rem .6rem !important;
}
.ts-wrapper.focus .ts-control{
  border-color:rgba(252,114,11,.7) !important;
  box-shadow:0 0 0 .25rem rgba(252,114,11,.13) !important;
}
.ts-control input{ color:var(--text) !important; }
.ts-control > input{ padding: 9px !important; }
.ts-control input::placeholder{ color:var(--text); opacity:.5; }
.ts-control > div{
  background:rgba(252,114,11,.2) !important;
  border:1px solid rgba(252,114,11,.55) !important;
  color:var(--text) !important;
  border-radius:999px !important;
  padding:.2rem .55rem !important;
}
.ts-wrapper.multi .ts-control > div{ margin: 6px 6px 6px 6px; }
.ts-wrapper.plugin-remove_button:not(.rtl) .item{ padding-right: 5px !important; }
.ts-control > div .remove{ border-left:0 !important; color:var(--muted) !important; }
.ts-control > div .remove:hover{ color:var(--text) !important; background:transparent !important; }
.ts-dropdown{
  background:var(--bg-1) !important;
  border:1px solid var(--border) !important;
  color:var(--text) !important;
  border-radius:var(--radius-md) !important;
  box-shadow:0 18px 50px rgba(0,0,0,.5);
}
.ts-dropdown .option{ color:var(--text) !important; }
.ts-dropdown .option:hover, .ts-dropdown .option.active{ background:rgba(252,114,11,.18) !important; }
.ts-dropdown .create{ color:var(--muted) !important; }
[data-theme="light"] .ts-control{ background:rgba(255,255,255,.7) !important; }

.captain-icon{
  width:18px;
  height:18px;
  margin-left:6px;
  vertical-align:-2px;
  cursor:help;
  transition:transform .15s ease;
}

.captain-icon:hover{
  transform:scale(1.15);
}

@media (max-width:576px){
  .captain-icon{
    width:16px;
    height:16px;
  }
}

/* Selector de asistencia: Presente / Retardo / Ausente */
.attendance-status{
  display: inline-flex;
  gap: .35rem;
  flex-wrap: wrap;
  justify-content: center;
}
.attendance-status input{
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
  pointer-events: none;
}
.attendance-status label{
  cursor: pointer;
  padding: .32rem .7rem;
  border-radius: 999px;
  border: 1px solid var(--border);
  font-size: var(--fs-xs);
  font-weight: 700;
  color: var(--muted);
  background: rgba(255,255,255,.03);
  transition: filter .15s ease, background .15s ease, border-color .15s ease, color .15s ease;
  user-select: none;
  white-space: nowrap;
}
.attendance-status label:hover{ filter: brightness(1.15); }
.attendance-status input:focus-visible + label{ outline: 2px solid var(--ads); outline-offset: 2px; }
.attendance-status input:checked + label.status-present{
  background: rgba(34,197,94,.25); border-color: rgba(34,197,94,.75); color: var(--text);
}
.attendance-status input:checked + label.status-late{
  background: rgba(252,114,11,.25); border-color: rgba(252,114,11,.8); color: var(--text);
}
.attendance-status input:checked + label.status-absent{
  background: rgba(244,44,44,.25); border-color: rgba(244,44,44,.8); color: var(--text);
}

/* Badges neón para porcentajes de asistencia / entrega de tareas */
.badge-neon{
  display: inline-block;
  padding: .3rem .75rem;
  border-radius: 999px;
  font-weight: 800;
  font-size: var(--fs-sm);
  border: 1px solid transparent;
}
.badge-neon-green{ background: rgba(34,197,94,.18); border-color: rgba(34,197,94,.85); color: #4ade80; box-shadow: 0 0 12px rgba(34,197,94,.35); }
.badge-neon-blue{ background: rgba(59,130,246,.18); border-color: rgba(59,130,246,.85); color: #60a5fa; box-shadow: 0 0 12px rgba(59,130,246,.35); }
.badge-neon-orange{ background: rgba(252,114,11,.18); border-color: rgba(252,114,11,.85); color: #fb923c; box-shadow: 0 0 12px rgba(252,114,11,.35); }
.badge-neon-yellow{ background: rgba(234,179,8,.18); border-color: rgba(234,179,8,.85); color: #facc15; box-shadow: 0 0 12px rgba(234,179,8,.35); }
.badge-neon-red{ background: rgba(244,44,44,.18); border-color: rgba(244,44,44,.85); color: #f87171; box-shadow: 0 0 12px rgba(244,44,44,.35); }
/* Punto sutil (no badge de texto) para marcar a un integrante inactivo junto a su nombre en tablas
   de equipo (Mi equipo, Equipos admin) — el porqué va en el tooltip de [data-tooltip], no en texto
   visible, para no competir con el nombre. */
.inactive-dot{
  display: inline-block;
  width: 7px; height: 7px;
  margin-left: .4rem;
  border-radius: 50%;
  background: var(--joven);
  box-shadow: 0 0 6px 1px rgba(244,44,44,.75);
  vertical-align: middle;
  cursor: help;
}
/* Columna "Últimos 5" del Reporte de asistencia: un círculo compacto por cada una de las últimas
   reuniones, de más antigua (izquierda) a más reciente (derecha). Mismo esquema de color que los
   badges neón (verde/amarillo/rojo) pero en formato punto con la inicial del status adentro, para
   caber 5 en una sola celda sin ensanchar la tabla. */
.att-last5{
  display: inline-flex;
  align-items: center;
  gap: .3rem;
}
.att-dot{
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px; height: 22px;
  border-radius: 50%;
  font-size: 10px;
  font-weight: 800;
  line-height: 1;
  border: 1px solid transparent;
  cursor: help;
  flex-shrink: 0;
}
.att-dot-present{ background: rgba(34,197,94,.18); border-color: rgba(34,197,94,.85); color: #4ade80; }
.att-dot-late{ background: rgba(234,179,8,.18); border-color: rgba(234,179,8,.85); color: #facc15; }
.att-dot-absent{ background: rgba(244,44,44,.18); border-color: rgba(244,44,44,.85); color: #f87171; }
/* El círculo más reciente (el de más a la derecha) lleva un anillo blanco y es ligeramente más
   grande, para distinguirlo de un vistazo de los otros 4 sin depender solo del orden. */
.att-dot-latest{
  width: 26px; height: 26px;
  font-size: 11px;
  box-shadow: 0 0 0 2px transparent, 0 0 10px 1px rgba(255,255,255,.2);
}
/* Pastel: mismo tratamiento neón pero con tonos más suaves, para el género (Chicas/Chicos). */
.badge-neon-pastel-pink{ background: rgba(244,114,182,.18); border-color: rgba(244,114,182,.75); color: #f9a8d4; box-shadow: 0 0 12px rgba(244,114,182,.3); }
.badge-neon-pastel-blue{ background: rgba(125,211,252,.18); border-color: rgba(125,211,252,.75); color: #a5e0fb; box-shadow: 0 0 12px rgba(125,211,252,.3); }
/* Medalla de ranking (1º-3º lugar) para la tarjeta de "Torneo > Equipos" — oro/plata/bronce. */
.badge-neon-gold{ background: rgba(234,179,8,.2); border-color: rgba(234,179,8,.85); color: #facc15; box-shadow: 0 0 12px rgba(234,179,8,.35); }
.badge-neon-silver{ background: rgba(203,213,225,.2); border-color: rgba(203,213,225,.85); color: #e2e8f0; box-shadow: 0 0 12px rgba(203,213,225,.35); }
.badge-neon-bronze{ background: rgba(217,119,6,.2); border-color: rgba(217,119,6,.85); color: #fb923c; box-shadow: 0 0 12px rgba(217,119,6,.35); }
/* Badge de grupo (Adolescentes/Jóvenes) con el look&feel neón, para el directorio y el detalle de
   persona — toma el color real del grupo (--group-color, cacheado por groupColorVar()) y cae al
   naranja/rojo fijo del sistema si aún no se cargó. */
.badge-neon-ads{
  background: color-mix(in srgb, var(--group-color, var(--ads)) 18%, transparent);
  border-color: color-mix(in srgb, var(--group-color, var(--ads)) 80%, transparent);
  color: var(--group-color, var(--ads));
  box-shadow: 0 0 12px color-mix(in srgb, var(--group-color, var(--ads)) 30%, transparent);
}
.badge-neon-joven{
  background: color-mix(in srgb, var(--group-color, var(--joven)) 18%, transparent);
  border-color: color-mix(in srgb, var(--group-color, var(--joven)) 80%, transparent);
  color: var(--group-color, var(--joven));
  box-shadow: 0 0 12px color-mix(in srgb, var(--group-color, var(--joven)) 30%, transparent);
}
/* Mismo look que .badge-neon-ads/.badge-neon-joven, pero para cualquier grupo "administrativo"
   (p.ej. "Encargado", ver classifyGroupWithAdmin()) que no sea ni Adolescentes ni Jóvenes —
   badgeClass()/groupBadge() la usan de fallback en vez de asumir el rojo de Jóvenes. */
.badge-neon-dynamic{
  background: color-mix(in srgb, var(--group-color, var(--joven)) 18%, transparent);
  border-color: color-mix(in srgb, var(--group-color, var(--joven)) 80%, transparent);
  color: var(--group-color, var(--joven));
  box-shadow: 0 0 12px color-mix(in srgb, var(--group-color, var(--joven)) 30%, transparent);
}

/* Tendencia de ranking en el tab de Competencia */
.trend-up{ color:#4ade80; font-weight:800; }
.trend-down{ color:#f87171; font-weight:800; }
.trend-same{ color: var(--muted); font-weight:700; }

/* Fila expandible de equipos */
.team-row-toggle{
  cursor: pointer;
  background: transparent;
  border: 1px solid var(--border);
  color: var(--text);
  border-radius: 999px;
  width: 28px;
  height: 28px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: transform .15s ease;
}
.team-row-toggle.expanded{ transform: rotate(90deg); }
.team-members-row td{
  background: rgba(255,255,255,.02);
  padding: 0 !important;
}
.team-members-row .table{ margin-bottom: 0; }

/* ---------------- Nueva vista de usuario: KPI cards, avatar de cuenta, dropdowns ---------------- */
.kpi-card{
  display: flex;
  flex-direction: column;
  gap: .25rem;
}
.kpi-icon{
  width: 38px;
  height: 38px;
  border-radius: var(--radius-md);
  display: flex;
  align-items: center;
  justify-content: center;
  background: linear-gradient(135deg, color-mix(in srgb, var(--group-color-1, var(--ads)) 8%, transparent), color-mix(in srgb, var(--group-color-3, var(--joven)) 8%, transparent));
  color: var(--group-color-1, var(--ads));
  font-size: 1.1rem;
  margin-bottom: .35rem;
}
.account-avatar{
  width: 28px;
  height: 28px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: linear-gradient(135deg, var(--ads), var(--joven));
  color: var(--text);
  font-weight: 600;
  font-size: var(--fs-xs);
}

.kpi-card .display-6 {
  font-weight: 600;
}

.kpi-card .small-muted {
  font-weight: 400;
  color: var(--text);
}

/* En escritorio, título (small-muted) y valor (display-6) de un KPI nunca deben partirse a una
   segunda línea — se recortan con "…" si el texto/número es muy largo. En celular sí se deja
   partir en varias líneas (el ancho de la tarjeta es mucho más chico ahí, cortar con ellipsis
   sería perder información demasiado seguido). */
.kpi-card .small-muted,
.kpi-card .display-6 {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
@media (max-width: 576px){
  .kpi-card .small-muted,
  .kpi-card .display-6 {
    white-space: normal;
  }
  /* Título del KPI más chico en mobile — la tarjeta es angosta ahí y el label competía en tamaño
     con el valor grande de abajo. */
  .kpi-card .small-muted{ font-size: var(--fs-xs); }
  /* Excepción a "sí se deja partir en varias líneas" de arriba: cuando el VALOR trae el escudo de
     un equipo (teamNameWithIcon()/personNameWithShield() en app.js — "Campeón al momento", etc.)
     el nombre queda partido justo a la mitad, pegado al escudo, y se ve mal — este caso puntual sí
     se recorta con "…" en una sola línea, como en escritorio. */
  .kpi-card:has(.team-shield) .display-6{
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
}

/* Dropdowns (Torneo, cuenta) con la misma superficie de cristal que el resto de la app */
.dropdown-menu.dropdown-menu-dark{
  background: linear-gradient(160deg, rgba(255,255,255,.08), rgba(255,255,255,.025)), var(--bg-0);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-hover);
  --bs-dropdown-link-color: var(--text);
  --bs-dropdown-link-hover-color: var(--text);
  --bs-dropdown-link-hover-bg: rgba(255,255,255,.08);
  /* Sin esto, .dropdown-item:active/.active (p.ej. al mantener presionado un item, o si alguno
     llegara a marcarse como el activo) cae en el azul default de Bootstrap
     (--bs-dropdown-link-active-bg) — se cambia al mismo tinte del color de grupo/acento que ya usa
     el resto de estados "activo" de la app (.btn-neon.active, .notif-item-icon, etc.). */
  --bs-dropdown-link-active-color: var(--text);
  --bs-dropdown-link-active-bg: color-mix(in srgb, var(--group-color-1, var(--ads)) 16%, transparent);
  --bs-dropdown-divider-bg: var(--border);
}
[data-theme="light"] .dropdown-menu.dropdown-menu-dark{
  background: linear-gradient(160deg, rgba(255,255,255,.94), rgba(255,255,255,.72)), var(--bg-1);
  color: var(--text);
}
.dropdown-item{ border-radius: var(--radius-sm); }
/* Dropdowns del navbar (cuenta, Torneo, etc.) también se abren al pasar el mouse, sin esperar
   click — el click/teclado que ya maneja Bootstrap se conserva como respaldo (touch/accesibilidad). */
@media (hover:hover){
  .navbar .dropdown:hover > .dropdown-menu{ display:block; margin-top:0; }
  /* Sin esto, un dropdown-menu-end mostrado por hover (sin que Popper lo haya posicionado, ya que
     Popper solo corre cuando Bootstrap abre el menú por JS) cae en la posición default de
     .dropdown-menu (pegado a la izquierda del botón, abriendo hacia la derecha) — el menú de
     cuenta, al ser el último elemento a la derecha del navbar, se saldría de la pantalla. Forzamos
     el mismo lado que usa Popper una vez abierto por click. */
  .navbar .dropdown:hover > .dropdown-menu.dropdown-menu-end{ right:0; left:auto; }
}
/* Agrupa visualmente el menú de navegación del navbar, separado tanto del logo como del bloque
   de tema+notificaciones+cuenta a la derecha (referencia: navbars con 3 zonas). */
.navbar-menu-group{
  display:flex; align-items:center; gap:.25rem;
  padding:.25rem .5rem; border-radius:999px;
  background: rgba(255,255,255,.04);
  border: 1px solid var(--border);
}
[data-theme="light"] .navbar-menu-group{ background: rgba(0,0,0,.03); }

/* Pestañas (.nav.nav-pills) de sub-tabs de cada sección. En móvil, si no caben en una fila se
   deslizan horizontalmente (scroll nativo por swipe) en vez de pasar a una segunda fila — la barra
   de scroll se oculta para que se vea como una fila de pills, no como un contenedor con scroll
   visible. .navbar-menu-group (el menú principal del navbar) comparte estas mismas clases pero en
   móvil se excluye de esta regla: se convierte en la barra de tabs fija de abajo (ver más abajo),
   no en una fila que se desliza. */
@media (max-width: 767.98px){
  .nav.nav-pills:not(.navbar-menu-group){
    flex-wrap: nowrap !important;
    overflow-x: auto;
    overflow-y: hidden;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none; /* Firefox */
    -ms-overflow-style: none; /* Edge/IE viejo */
  }
  .nav.nav-pills:not(.navbar-menu-group)::-webkit-scrollbar{ display: none; height: 0; } /* Chrome/Safari */
  .nav.nav-pills:not(.navbar-menu-group) > .nav-item{ flex: 0 0 auto; }
}

/* En móvil, el menú principal del navbar (Inicio/Aula/Torneo/Ahorro/Premios, o su equivalente de
   usuario) se convierte en una barra de tabs fija abajo, como el tab bar de una app nativa —
   arriba en el navbar solo quedan el logo, cambiar modo, notificaciones y el avatar. Mismo patrón
   ya usado para .intro-footer-bar (bienvenida.php): position:fixed + blur + borde superior, y el
   body gana el padding-bottom equivalente para que el contenido no quede tapado. */
@media (max-width: 767.98px){
  /* .navbar-glass.is-scrolled agrega backdrop-filter (ver arriba) al <nav>, y backdrop-filter crea
     un nuevo "containing block" para sus descendientes position:fixed — sin esto, en cuanto se hace
     scroll la barra de abajo (que SÍ es descendiente del <nav>) deja de fijarse al viewport y salta
     a fijarse dentro del <nav> mismo (que mide lo alto del navbar de arriba), apareciendo pegada
     arriba en vez de abajo. Se desactiva el blur en el navbar solo en móvil para que la barra de
     tabs de abajo se quede fija donde debe. */
  .navbar-glass.is-scrolled{ backdrop-filter: none; }
  .navbar-menu-group{
    position: fixed;
    left: 0; right: 0; bottom: 0; top: auto;
    z-index: 1030;
    width: 100%;
    max-width: none;
    /* !important: el HTML de este mismo elemento trae "my-2" (margen vertical de Bootstrap para
       cuando vive en el flujo normal del navbar de escritorio) — esa utilidad se compila CON
       !important, así que un margin:0 normal no le ganaba y dejaba la barra fija separada del
       borde inferior real de la pantalla por esos 8px, con el fondo de la página asomándose
       debajo en ese hueco. */
    margin: 0 !important;
    border-radius: 0;
    border: none;
    border-top: 1px solid var(--border);
    background: rgba(10,13,20,.92);
    backdrop-filter: blur(10px);
    padding: .35rem .25rem calc(.35rem + env(safe-area-inset-bottom));
    gap: 0;
    justify-content: space-around;
    overflow: visible;
  }
  [data-theme="light"] .navbar-menu-group{ background: rgba(255,255,255,.92); }
  .navbar-menu-group .nav-item{ flex: 1 1 0; min-width: 0; display: flex; }
  /* Ícono arriba, etiqueta abajo (siempre visible, ya no icon-only) — el patrón clásico de tab bar
     nativo, en vez de la píldora horizontal icono+texto que usan el resto de .btn-neon. */
  .navbar-menu-group .btn-neon{
    flex: 1 1 auto; width: 100%;
    display: flex; flex-direction: column; align-items: center; justify-content: center; gap: .2rem;
    background: transparent !important;
    border: none !important;
    box-shadow: none !important;
    border-radius: var(--radius-sm) !important;
    padding: .3rem .1rem !important;
    font-size: .68rem;
    line-height: 1.1;
    color: var(--muted) !important;
  }
  .navbar-menu-group .btn-neon i{ font-size: 1.2rem; }
  .navbar-menu-group .btn-neon .d-none.d-md-inline{ display: inline !important; }
  .navbar-menu-group .dropdown-toggle::after{ display: none; } /* sin la flechita de Bootstrap: no calza con el ícono arriba/etiqueta abajo */
  /* Estado activo: solo cambia el color de ícono+etiqueta (como cualquier tab bar nativo), no la
     píldora de degradado que .btn-neon.active usa en el resto de la app. */
  .navbar-menu-group .btn-neon.active,
  .navbar-menu-group .dropdown-toggle.active{
    background: transparent !important;
    box-shadow: none !important;
    color: var(--group-color-1, var(--ads)) !important;
    font-weight: 700 !important;
  }
  .navbar-menu-group .btn-neon:hover{ background: transparent !important; filter: none !important; }
  /* El menú (Aula/Torneo) debe abrir hacia arriba: la barra está pegada abajo, sin espacio debajo.
     Bootstrap desactiva Popper para dropdowns dentro de un <nav class="navbar"> (su detección
     "inNavbar"), así que nunca se auto-voltea con espacio insuficiente — se fuerza aquí con CSS
     puro, igual que su propia clase .dropup (top:auto + bottom:100%). */
  .navbar-menu-group .dropdown-menu{ top: auto; bottom: 100%; margin-bottom: .5rem; }
  /* :has() y no un simple "body": esta reserva de espacio es para que el contenido no quede
     tapado DEBAJO de la barra de tabs fija (.navbar-menu-group, ver arriba) — pero páginas sin
     navbar, como login.html, no tienen esa barra, así que este padding ahí era puro espacio
     vacío al final: la tarjeta de login se veía "cortada"/con scroll de más sin necesitarlo. */
  body:has(.navbar-menu-group){ padding-bottom: calc(4.25rem + env(safe-area-inset-bottom)); }
}

/* El avatar del dropdown de cuenta no va "encerrado" en un botón con el fondo/borde pill de
   .btn-neon como el resto de íconos del navbar — es el avatar mismo, sin chrome alrededor, del
   mismo diámetro que el botón de "Cambiar tema"/campana de al lado (~42px, medido) y
   perfectamente redondo. */
.navbar-avatar-btn.btn.btn-neon{
  background: transparent !important;
  border: none !important;
  box-shadow: none !important;
  padding: 0 !important;
  width: 42px; height: 42px;
  border-radius: 50% !important;
  display: inline-flex; align-items: center; justify-content: center;
  overflow: hidden;
}
.navbar-avatar-btn.btn.btn-neon:hover{ background: transparent !important; filter: brightness(1.15) !important; }
.navbar-avatar-img{ width: 100%; height: 100%; }
.navbar-avatar-img.account-avatar{ display:inline-flex; align-items:center; justify-content:center; font-size: 1rem; }

/* Panel de vista previa de notificaciones (campana del navbar admin): lista corta de los
   comentarios más recientes, con "Ver todas las notificaciones" al final para llegar a la sección
   completa (admin-notifications.php) — igual que el resto de dropdowns del navbar, se abre con
   hover o click. */
/* Se ancla siempre al viewport (no al botón de la campana) en vez de dejar que Bootstrap/Popper la
   posicione relativa al botón: la campana no es el último elemento del navbar (el avatar va
   después), así que un dropdown-menu-end normal abre midiendo desde ESE punto y en pantallas no
   tan anchas el panel se sale por la izquierda — con position:fixed siempre queda pegada arriba,
   visible completa sin importar el ancho de pantalla. El borde derecho (--notif-panel-right) lo
   calcula show.bs.dropdown en app.js a partir de la posición real del botón de la campana (en vez
   de un valor fijo), para que el panel siempre cargue pegado hacia la izquierda del botón sin
   importar dónde caiga éste en el navbar. */
.notif-panel{
  position: fixed !important;
  top: 4.5rem !important;
  right: var(--notif-panel-right, 1rem) !important;
  left: auto !important;
  transform: none !important;
  width: 400px;
  max-width: calc(100vw - 2rem);
  padding: 0;
}
.notif-panel-header{
  display:flex; align-items:center; justify-content:space-between;
  flex-wrap: nowrap; gap: .75rem;
  padding: .85rem 1rem; border-bottom: 1px solid var(--border);
}
/* El botón "Marcar todas como leídas" usa .btn-link de Bootstrap (que trae su azul default,
   --bs-link-color) — se sobreescribe al acento de la app en vez de dejarlo en ese azul, y se evita
   que su texto se rompa en dos líneas junto al título "Notificaciones" (ver el ancho del panel y
   el gap de arriba). */
#markAllFeedbackReadBtn{
  color: var(--ads) !important;
  white-space: nowrap;
  text-decoration: none !important;
}
#markAllFeedbackReadBtn:hover{ filter: brightness(1.15); text-decoration: underline !important; }
.notif-panel-list{ max-height: 360px; overflow-y: auto; }
.notif-item{
  display:flex; gap:.65rem; align-items:flex-start;
  padding: .75rem 1rem; border-bottom: 1px solid var(--border);
}
.notif-item:last-child{ border-bottom: none; }
.notif-item.is-unread{ background: rgba(255,255,255,.04); }
[data-theme="light"] .notif-item.is-unread{ background: rgba(0,0,0,.03); }
.notif-item-icon{
  width: 34px; height: 34px; flex-shrink:0; border-radius:50%;
  display:flex; align-items:center; justify-content:center;
  background: linear-gradient(135deg, color-mix(in srgb, var(--group-color-1, var(--ads)) 16%, transparent), color-mix(in srgb, var(--group-color-3, var(--joven)) 16%, transparent));
  color: var(--group-color-1, var(--ads));
}
.notif-item-body{ min-width:0; flex:1; }
.notif-item-title{ font-weight:600; font-size:.9rem; }
.notif-item-desc{ font-size:.8rem; color: var(--muted); overflow:hidden; text-overflow:ellipsis; display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; }
.notif-item-time{ font-size:.72rem; color: var(--muted); white-space:nowrap; flex-shrink:0; }
.notif-panel-footer{ text-align:center; padding: .7rem 1rem; border-top: 1px solid var(--border); }

/* Ajustes del sistema: sidebar vertical en vez de las pestañas horizontales (Accesos, Grupos,
   Comboboxes, Opciones preestablecidas, Modales, Redes sociales, Registros, Responsabilidades). */
.system-layout{ display:flex; gap:1.5rem; align-items:flex-start; }
.system-sidebar{
  flex: 0 0 240px;
  display:flex; flex-direction:column;
  position: sticky; top: 85px;
}
/* Lista plana con secciones agrupadas (Cuentas/Formularios/Contenido/Sistema), en vez de pills —
   cada link ocupa todo el ancho, sin fondo en reposo, resaltado sutil (tinte del color de acento)
   cuando está activo, como las referencias de sidebar de ajustes adjuntas. */
.system-sidebar-group + .system-sidebar-group{ margin-top: 1.1rem; }
.system-sidebar-label{
  font-size: var(--fs-xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .05em;
  color: var(--muted);
  padding: 0 .75rem;
  margin-bottom: .35rem;
}
.system-sidebar .btn-neon{
  justify-content:flex-start; text-align:left; width:100%;
  background: transparent !important;
  border: none !important;
  border-radius: var(--radius-sm) !important;
  color: var(--text) !important;
  font-weight: 500 !important;
  padding: .55rem .75rem !important;
  margin-bottom: .1rem;
}
.system-sidebar .btn-neon:hover{ background: rgba(255,255,255,.06) !important; }
[data-theme="light"] .system-sidebar .btn-neon:hover{ background: rgba(0,0,0,.05) !important; }
.system-sidebar .btn-neon.active{
  background: color-mix(in srgb, var(--group-color-1, var(--ads)) 14%, transparent) !important;
  color: var(--group-color-1, var(--ads)) !important;
  font-weight: 700 !important;
  border: solid 1px var(--group-color-1, var(--ads)) !important;
}
.system-content{ flex:1 1 0; min-width:0; }
/* En móvil, en vez de envolver los 9 links en una cuadrícula de varias filas (se veía apretado y
   desordenado sin las etiquetas de grupo), se aplica el mismo criterio que las pestañas de arriba:
   una sola fila que se desliza horizontalmente, con la barra de scroll oculta. */
@media (max-width: 767.98px){
  .system-layout{ flex-direction:column; }
  .system-sidebar{
    /* .system-layout pasa a columna arriba: el eje principal de .system-sidebar (antes horizontal)
       ahora es vertical, así que el flex-basis:240px de escritorio (pensado para el ANCHO de la
       columna lateral) se convertiría en una ALTURA fija de 240px — y con align-items:stretch
       (default) estiraría cada pill de esta sola fila hasta esa altura. Se resetea a "auto" para
       que la altura vuelva a salir del contenido, como cualquier fila normal de pills. */
    flex:0 0 auto;
    flex-direction:row; flex-wrap:nowrap; position:static; width:100%; gap:.4rem;
    overflow-x:auto; overflow-y:hidden; -webkit-overflow-scrolling:touch;
    scrollbar-width:none; -ms-overflow-style:none;
    padding-bottom:.15rem;
  }
  .system-sidebar::-webkit-scrollbar{ display:none; height:0; }
  .system-sidebar-group{ display:contents; }
  .system-sidebar-label{ display:none; }
  .system-sidebar .btn-neon{
    flex:0 0 auto; width:auto; white-space:nowrap;
    border-radius:999px !important; padding:.55rem .9rem !important;
  }
}
.form-switch .form-check-input{
  width: 2.4em;
  cursor: pointer;
}
.form-switch .form-check-input:checked{
  background-color: var(--ads);
  border-color: var(--ads);
}

/* Sub-navegación de Aula/Teams dentro de la vista de usuario */
.user-tab{ padding-top: .5rem; }

.section-divider{
  margin: 1rem 0 .5rem;
  color: inherit;
  border: 0;
  border-top: var(--bs-border-width) solid;
  opacity: .25;
}
/* Separador angosto dentro de una columna del footer (p.ej. entre el logo y las redes sociales,
   o entre Adolescentes/Jóvenes en "Horarios de reunión") — más discreto que .section-divider. */
.divider{
  margin: .75rem 0;
  color: inherit;
  border: 0;
  border-top: var(--bs-border-width) solid;
  opacity: .15;
}

/* Logo oficial en los navbars de usuario y administrador — mismo tamaño que el avatar del
   dropdown de cuenta (ver el style width:40px/height:40px del avatar dentro del menú, en
   renderAccountDropdown()/renderAdminAccountDropdown()) para que ambos círculos del navbar
   luzcan parejos. */
.navbar-logo{
  height: 40px;
  width: 40px;
  object-fit: contain;
  border-radius: 50%;
}
/* "Jóvenes"/"Adolescentes"/"Administrador" junto al logo (ver #navbarGroupLabel, resuelto por
   renderAccountDropdown()/renderAdminAccountDropdown() en app.js según el grupo o rol de la
   cuenta). */
.navbar-group-label{
  font-weight: 700;
  font-size: 1.5rem;
  color: var(--text);
  white-space: nowrap;
}


/* Variantes de botón para acciones de editar/eliminar en tablas de administración */
/* Pestañas selectoras de equipo en Torneo > Equipos: mismo look&feel neón de siempre, pero
   coloreadas con el color real de CADA equipo (--team-color) en vez del naranja/rojo genérico del
   sistema — así se distingue de un vistazo cuál pestaña es cuál equipo, con su escudo incluido. */
.team-tab-neon{
  border: 1px solid color-mix(in srgb, var(--team-color, var(--muted)) 60%, transparent) !important;
  border-radius: 999px !important;
  background: color-mix(in srgb, var(--team-color, var(--muted)) 14%, transparent) !important;
  color: var(--text) !important;
  transition: transform .2s ease, filter .2s ease, background .2s ease, border-color .2s ease !important;
}
.team-tab-neon:hover{
  background: color-mix(in srgb, var(--team-color, var(--muted)) 28%, transparent) !important;
  filter: brightness(1.1) !important;
}
.team-tab-neon.active{
  background: var(--team-color, var(--ads)) !important;
  border-color: transparent !important;
  color: #0b0e14 !important;
  font-weight: 800 !important;
  box-shadow: 0 10px 30px color-mix(in srgb, var(--team-color, var(--ads)) 40%, transparent);
}
[data-theme="light"] .team-tab-neon.active{ color: #08111e !important; }
/* El escudo (imagen subida) se pierde contra el fondo sólido del color del equipo cuando la
   pestaña está activa/seleccionada — se fuerza a negro para que siempre se distinga. */
.team-tab-neon.active img{ filter: brightness(0); }

/* Botón "Guardar" (feedback de éxito): verde neón, mismo look&feel que el resto de .btn-neon-*. */
.btn-neon-green{
  border: 1px solid rgba(34,197,94,.7) !important;
  border-radius: 999px !important;
  background: rgba(34,197,94,.14) !important;
  color: #4ade80 !important;
  transition: transform .2s ease, filter .2s ease, background .2s ease, border-color .2s ease !important;
}
.btn-neon-green:hover{
  background: rgba(34,197,94,.28) !important;
  border-color: #4ade80 !important;
  filter: brightness(1.08) !important;
  color: #4ade80 !important;
}
.btn-neon-yellow{
  border: 1px solid rgba(234,179,8,.7) !important;
  border-radius: 999px !important;
  background: rgba(234,179,8,.14) !important;
  color: #facc15 !important;
  transition: transform .2s ease, filter .2s ease, background .2s ease, border-color .2s ease !important;
}
.btn-neon-yellow:hover{
  background: rgba(234,179,8,.28) !important;
  border-color: #facc15 !important;
  filter: brightness(1.08) !important;
  color: #facc15 !important;
}
.btn-neon-red{
  border: 1px solid rgba(244,44,44,.7) !important;
  border-radius: 999px !important;
  background: rgba(244,44,44,.14) !important;
  color: #f87171 !important;
  transition: transform .2s ease, filter .2s ease, background .2s ease, border-color .2s ease !important;
}
.btn-neon-red:hover{
  background: rgba(244,44,44,.28) !important;
  border-color: #f87171 !important;
  filter: brightness(1.08) !important;
  color: #f87171 !important;
}
/* Botón "Limpiar filtros": rosa pastel neón, en toda la app */
.btn-neon-pink{
  border: 1px solid rgba(244,114,182,.7) !important;
  border-radius: 999px !important;
  background: rgba(244,114,182,.14) !important;
  color: #f9a8d4 !important;
  transition: transform .2s ease, filter .2s ease, background .2s ease, border-color .2s ease !important;
}
.btn-neon-pink:hover{
  background: rgba(244,114,182,.28) !important;
  border-color: #f9a8d4 !important;
  filter: brightness(1.08) !important;
  color: #f9a8d4 !important;
}
/* Botón "Buscar" (antes "Cargar"): azul neón, para acciones de consulta/búsqueda */
.btn-neon-blue{
  border: 1px solid rgba(56,189,248,.7) !important;
  border-radius: 999px !important;
  background: rgba(56,189,248,.14) !important;
  color: #38bdf8 !important;
  transition: transform .2s ease, filter .2s ease, background .2s ease, border-color .2s ease !important;
}
.btn-neon-blue:hover{
  background: rgba(56,189,248,.28) !important;
  border-color: #7dd3fc !important;
  filter: brightness(1.08) !important;
  color: #7dd3fc !important;
}

/* Paginación de tablas: tematizada para combinar con las tarjetas oscuras */
.pagination .page-link{
  border: 1px solid var(--border);
  background: rgba(255,255,255,.04);
  color: var(--text);
  border-radius: 999px !important;
  margin: 0 .15rem;
  min-width: 2.25rem;
  text-align: center;
}
.pagination .page-link:hover{
  background: rgba(255,255,255,.14);
  border-color: var(--border-hover);
  color: var(--text);
}
.pagination .page-item.active .page-link{
  background: linear-gradient(90deg, var(--ads), var(--joven));
  border-color: transparent;
  color: #ffffff;
  font-weight: 800;
}
.pagination .page-item.disabled .page-link{
  background: transparent;
  color: var(--muted);
  opacity: .5;
  border-color: var(--border);
}
.pagination .page-link:focus{ box-shadow: 0 0 0 .2rem rgba(252,114,11,.18); }

/* Fila de paginación: la paginación queda centrada respecto a la tabla y el selector de
   filas por página se posiciona a la derecha, sin que uno afecte al centrado del otro. */
.pager-row{
  position: relative;
  min-height: 2.25rem;
}
.pager-row .pagination{
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  margin: 0 auto;
}
.pager-row .page-size-select{
  margin-left: auto;
  position: relative;
  z-index: 1;
}
@media (max-width: 575px){
  .pager-row .pagination{ position: static; transform: none; margin: 0 auto; }
  .pager-row{ flex-direction: column; align-items: center !important; }
  .pager-row .page-size-select{ margin-left: 0; }
}
/* Cuando una tabla paginada no tiene nada que mostrar en su pager (menos de 10 filas,
   una sola página y sin leyenda de resultados), renderPagerHtml() deja el contenedor vacío;
   sin esto, su margin-top seguiría reservando un espacio en blanco antiestético. */
[id$="Pager"]:empty{ margin: 0 !important; }

/* Nombre/apodo de persona como hipervínculo a su perfil: en negritas, color de su grupo, sin subrayado */
.person-link{
  font-weight: 700;
  text-decoration: none;
  transition: filter .15s ease, opacity .15s ease;
}
.person-link:hover,
.person-link:focus-visible{
  filter: brightness(1.25);
  text-decoration: none;
}

/* Badge de equipo (icono + nombre), coloreado dinámicamente con el color del equipo */
.badge-team-neon{
  display: inline-flex;
  align-items: center;
  gap: .35rem;
  padding: .25rem .7rem;
  border-radius: 999px;
  font-weight: 700;
  font-size: var(--fs-sm);
  white-space: nowrap;
  text-decoration: none;
  color: var(--team-color, var(--muted));
  background: color-mix(in srgb, var(--team-color, var(--muted)) 18%, transparent);
  border: 1px solid color-mix(in srgb, var(--team-color, var(--muted)) 70%, transparent);
  box-shadow: 0 0 12px color-mix(in srgb, var(--team-color, var(--muted)) 30%, transparent);
  cursor: pointer;
  transition: filter .15s ease;
}
.badge-team-neon:hover,
.badge-team-neon:focus-visible{ filter: brightness(1.25); }

/* Escudo de equipo (solo ícono), mismo estilo neón que .badge-team-neon pero en formato compacto.
   El tamaño real (width/height/border-radius/font-size) lo fija teamShieldHtml() por instancia
   vía inline style, así se reutiliza igual en KPIs (≤35px), la tabla de equipos (38px) y el podio (100px). */
.team-shield{
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 38px;
  height: 38px;
  border-radius: 10px;
  font-size: 1.1rem;
  line-height: 1;
  vertical-align: middle;
  color: var(--team-color, var(--muted));
  background: color-mix(in srgb, var(--team-color, var(--muted)) 18%, transparent);
  border: 1px solid color-mix(in srgb, var(--team-color, var(--muted)) 70%, transparent);
  box-shadow: 0 0 12px color-mix(in srgb, var(--team-color, var(--muted)) 30%, transparent);
}
.team-shield img{ object-fit: cover; border-radius: 6px; }

/* Tabla de integrantes en el modal de editar equipo: menos apretada que un table-sm normal */
.team-edit-members-table td,
.team-edit-members-table th{
  padding-top: .7rem;
  padding-bottom: .7rem;
}

/* Muestra de color de equipo: chip + código hex */
.team-color-chip{
  display: inline-flex;
  align-items: center;
  gap: .4rem;
  font-family: var(--font-mono, monospace);
  font-size: var(--fs-sm);
}
.team-color-chip .swatch{
  width: 16px;
  height: 16px;
  border-radius: 5px;
  border: 1px solid rgba(255,255,255,.25);
  flex: none;
}
/* Columna "Colores" de Sistema > Grupos: solo el cuadrito (sin hexadecimal) + un botón chiquito
   para copiarlo al portapapeles — el tooltip (data-tooltip, ya global en toda la app) dice de qué
   color se trata. */
.color-swatch-group{ display:inline-flex; align-items:center; gap:.25rem; }
.color-swatch-group .swatch{
  width: 18px; height: 18px; border-radius: 5px;
  border: 1px solid rgba(255,255,255,.25);
  flex: none;
}
.btn-copy-swatch{
  border: none; background: transparent; padding: 0 .1rem;
  color: var(--muted); line-height: 1; font-size: .75rem;
  cursor: pointer;
}
.btn-copy-swatch:hover{ color: var(--text); }

/* Última fila de cualquier tabla: sin borde inferior */
.table > tbody > tr:last-child > td,
.table > tbody > tr:last-child > th{
  border-bottom: 0 !important;
}
/* Excepción: tablas con paginación, envueltas en una tarjeta con padding (detail-card p-3,
   mismo estilo que Asistencia > Resumen del periodo) — aquí sí conservan el borde de su
   última fila, para rematar contra el padding de la tarjeta en vez de quedar "cortadas".
   Solo cuando la tabla tiene 10 o más filas (misma referencia que el umbral del
   page-size-select); con menos de 10 filas se conserva el borde oculto de siempre. */
table:has(#podioTableBody):has(> tbody > tr:nth-child(10)) > tbody > tr:last-child > td,
table:has(#miEquipoTable):has(> tbody > tr:nth-child(10)) > tbody > tr:last-child > td,
table:has(#aulaAsistenciaHistory):has(> tbody > tr:nth-child(10)) > tbody > tr:last-child > td,
table:has(#aulaTareasTable):has(> tbody > tr:nth-child(10)) > tbody > tr:last-child > td,
table:has(#peopleTable):has(> tbody > tr:nth-child(10)) > tbody > tr:last-child > td,
table:has(#usersTable):has(> tbody > tr:nth-child(10)) > tbody > tr:last-child > td,
table:has(#auditTable):has(> tbody > tr:nth-child(10)) > tbody > tr:last-child > td,
table:has(#teamsTable):has(> tbody > tr:nth-child(10)) > tbody > tr:last-child > td,
table:has(#attendanceStatsTable):has(> tbody > tr:nth-child(10)) > tbody > tr:last-child > td,
table:has(#taskReportTable):has(> tbody > tr:nth-child(10)) > tbody > tr:last-child > td,
table:has(#tasksManageTable):has(> tbody > tr:nth-child(10)) > tbody > tr:last-child > td,
table:has(#competitionTableBody):has(> tbody > tr:nth-child(10)) > tbody > tr:last-child > td,
table:has(#attendanceSessionsTable):has(> tbody > tr:nth-child(10)) > tbody > tr:last-child > td,
table:has(#ageGroupsTable):has(> tbody > tr:nth-child(10)) > tbody > tr:last-child > td,
table:has(#savingsReportTable):has(> tbody > tr:nth-child(10)) > tbody > tr:last-child > td,
table:has(#donationsReportTable):has(> tbody > tr:nth-child(10)) > tbody > tr:last-child > td{
  border-bottom: 1px solid var(--border) !important;
}

/* Carrusel de KPIs (cuando hay más de 4). Posición relativa para que los botones floten encima
   sin restarle ancho al track — así el conjunto de 4 tarjetas mide igual que una fila normal. */
.kpi-carousel-wrap{
  position: relative;
}
.kpi-carousel-track{
  display: flex;
  gap: 1rem;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  -ms-overflow-style: none;
  scrollbar-width: none;
  padding-bottom: .25rem;
}
.kpi-carousel-track::-webkit-scrollbar{ display: none; }
.kpi-carousel-item{
  flex: 0 0 calc(25% - .75rem);
  min-width: 200px;
  scroll-snap-align: start;
}
@media (max-width: 991px){
  .kpi-carousel-item{ flex-basis: calc(50% - .5rem); }
}
@media (max-width: 575px){
  .kpi-carousel-item{ flex-basis: 85%; }
}
.kpi-carousel-btn{
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  z-index: 2;
  width: 34px;
  height: 34px;
  padding: 0 !important;
  border: 0;
  border-radius: 50%;
  background: linear-gradient(90deg, color-mix(in srgb, var(--group-color-1, var(--ads)) 80%, transparent), color-mix(in srgb, var(--group-color-3, var(--joven)) 80%, transparent));
  color: #ffffff;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 10px 30px color-mix(in srgb, var(--group-color-1, var(--ads)) 18%, transparent), 0 12px 32px color-mix(in srgb, var(--group-color-3, var(--joven)) 12%, transparent);
  transition: filter .15s ease;
}
.kpi-carousel-btn:hover{ filter: brightness(1.1); }
.kpi-carousel-prev{ left: -20px; }
.kpi-carousel-next{ right: -20px; }

@media (max-width: 767.98px){
  /* "Mi ahorro" tiene 5 KPIs (Semana actual/Semanas ahorradas/Semanas atrasadas/Mi ahorro/Adeudo),
     que en cualquier otra pantalla usan el carrusel de arriba (renderKpiRow() lo activa solo con
     más de 4) — pedido explícito: en mobile, esta sección en particular se ve mejor como grid
     normal (2 por fila, sin deslizar) en vez de carrusel. display:contents en el track "desaparece"
     ese div para efectos de layout sin quitarlo del DOM, así sus tarjetas quedan como hijos
     directos del flex-wrap de #ahorroKpis. El temporizador de auto-avance (initKpiCarousel()) sigue
     corriendo de fondo, pero scrollBy()/scrollTo() sobre un elemento sin overflow ya no hacen nada
     visible — no hace falta detenerlo aparte. */
  #ahorroKpis.kpi-carousel-wrap{ display: flex; flex-wrap: wrap; gap: .75rem; }
  #ahorroKpis.kpi-carousel-wrap .kpi-carousel-btn{ display: none; }
  #ahorroKpis .kpi-carousel-track{ display: contents; }
  #ahorroKpis .kpi-carousel-item{ flex: 0 0 calc(50% - .375rem); min-width: 0; scroll-snap-align: none; }
}

/* Filas de podio: oro / plata / bronce, con opacidad para conservar el look&feel oscuro */
tr.rank-gold > td{ background: rgba(234,179,8,.16) !important; }
tr.rank-silver > td{ background: rgba(203,213,225,.14) !important; }
tr.rank-bronze > td{ background: rgba(217,119,6,.16) !important; }

/* Modales centrados vertical y horizontalmente en todo el sistema */
.modal-dialog{
  display: flex;
  align-items: center;
  min-height: calc(100% - 3.5rem);
}

/* Toast notifications */
.toast-container{ z-index: 1080; }
.toast.app-toast{
  border: 1px solid var(--border);
  background: var(--card);
  backdrop-filter: blur(10px) saturate(140%);
  -webkit-backdrop-filter: blur(10px) saturate(140%);
  color: var(--text);
  min-width: 300px;
  box-shadow: 0 20px 50px rgba(0,0,0,.35);
}
.toast.app-toast .toast-body{
  display: flex;
  align-items: center;
  gap: .6rem;
}
.toast.app-toast .toast-icon{
  font-size: 1.2rem;
  flex: 0 0 auto;
}
.toast.app-toast.toast-success .toast-icon{ color: #4ade80; }
.toast.app-toast.toast-danger .toast-icon{ color: #f87171; }
.toast.app-toast.toast-warning .toast-icon{ color: #facc15; }
.toast.app-toast.toast-info .toast-icon{ color: #60a5fa; }
.toast.app-toast .btn-close{ filter: invert(1) grayscale(1); }
[data-theme="light"] .toast.app-toast .btn-close{ filter: none; }

/* Centrado de columnas en todas las tablas del sistema, salvo nombre/apodo de discípulo,
   nombre de tarea, acciones (que ya usan .text-end), y las columnas explícitamente listadas
   como "siempre a la izquierda": Team/Equipo, Día, Cumple, Grupo, Estatus, Notas, Usuario,
   Tipo de usuario. */
.table thead th,
.table tbody td{ text-align: center; }
.table thead th.text-end,
.table tbody td.text-end{ text-align: right; }
table:has(#homeBirthdays) thead th, #homeBirthdays td,
table:has(#inicioBirthdays) thead th, #inicioBirthdays td,
table:has(#peopleTable) thead th:nth-child(-n+3), #peopleTable td:nth-child(-n+3),
table:has(#usersTable) thead th:nth-child(-n+2), #usersTable td:nth-child(-n+2),
table:has(#attendanceTable) thead th:nth-child(-n+3), #attendanceTable td:nth-child(-n+3),
table:has(#attendanceTable) thead th:nth-child(6), #attendanceTable td:nth-child(6),
table:has(#attendanceStatsTable) thead th:first-child, #attendanceStatsTable td:first-child,
table:has(#taskReportTable) thead th:first-child, #taskReportTable td:first-child,
table:has(#taskTable) thead th:nth-child(-n+3), #taskTable td:nth-child(-n+3),
table:has(#miEquipoTable) thead th:first-child, #miEquipoTable td:first-child,
table:has(#aulaTareasTable) thead th, #aulaTareasTable td,
table:has(#aulaAsistenciaHistory) thead th:nth-child(2), #aulaAsistenciaHistory td:nth-child(2),
table:has(#tasksManageTable) thead th:nth-child(-n+3), #tasksManageTable td:nth-child(-n+3),
table:has(#competitionTableBody) thead th:nth-child(2), #competitionTableBody td:nth-child(2),
table:has(#podioTableBody) thead th:nth-child(2), #podioTableBody td:nth-child(2),
table:has(#homeCompetition) thead th:nth-child(2), #homeCompetition td:nth-child(2),
table:has(#inicioCompetition) thead th:nth-child(2), #inicioCompetition td:nth-child(2),
table:has(#auditTable) thead th:nth-child(-n+2), #auditTable td:nth-child(-n+2),
table:has(#te_members_table) thead th:nth-child(-n+2), #te_members_table td:nth-child(-n+2),
table:has(#teamsTable) thead th:nth-child(3), #teamsTable td:nth-child(3),
table:has(#ageGroupsTable) thead th:first-child, #ageGroupsTable td:first-child,
table:has(#savingsReportTable) thead th:first-child, #savingsReportTable td:first-child,
table:has(#donationsReportTable) thead th:first-child, #donationsReportTable td:first-child{
  text-align: left;
}

/* Podio visual (1º-2º-3º lugar) antes de la tabla de posiciones */
.podium-wrap{
  display: flex;
  align-items: flex-end;
  justify-content: center;
  gap: 3rem;
  flex-wrap: wrap;
  margin-bottom: 1.5rem;
}
.podium-step{
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: .5rem;
  width: 200px;
}
.podium-step .podium-icon{
  line-height: 1;
}
.podium-step .podium-name{
  font-weight: 700;
  text-align: center;
  font-size: var(--fs-lg);
}
.podium-step .podium-points{
  font-weight: 800;
  font-size: var(--fs-md);
}
.podium-block{
  width: 100%;
  border-radius: var(--radius-md) var(--radius-md) 0 0;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding-top: .6rem;
  color: #0b0e14;
  font-weight: 800;
  font-size: 3.4rem;
  box-shadow: 0 -10px 30px rgba(0,0,0,.18);
}
.podium-1{ order: 2; }
.podium-2{ order: 1; }
.podium-3{ order: 3; }
.podium-1 .podium-block{ height: 200px; background: linear-gradient(180deg, #fde68a, #eab308); }
.podium-2 .podium-block{ height: 145px; background: linear-gradient(180deg, #f1f5f9, #cbd5e1); }
.podium-3 .podium-block{ height: 105px; background: linear-gradient(180deg, #fdba74, #b45309); }

.detail-card ul li {
  font-weight: 200;
}

/* Tab "Mi ahorro": calendario (heatmap) de aportaciones semanales — una fila por mes, una celda
   por cada semana ISO de ese mes, coloreada según su estatus (ver computeSavingsWeeksGrid() en
   bootstrap.php). */
.savings-heatmap{ display: flex; flex-direction: column; gap: .5rem; }
.savings-month-row{ display: flex; align-items: center; gap: .75rem; }
.savings-month-label{ width: 92px; flex-shrink: 0; font-size: .82rem; font-weight: 600; color: var(--muted); }
.savings-week-cells{ display: flex; flex-wrap: wrap; gap: .4rem; flex: 1; }
.savings-week-cell{
  width: 64px;
  min-height: 52px;
  border-radius: 8px;
  padding: .35rem .25rem;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--border);
  background: rgba(255,255,255,.03);
  font-size: .72rem;
  line-height: 1.25;
  text-align: center;
}
.savings-week-num{ font-weight: 700; }
.savings-week-amount{ opacity: .85; }
.savings-cell-paid{ background: rgba(34,197,94,.18); border-color: rgba(34,197,94,.6); color: #4ade80; }
.savings-cell-late{ background: rgba(244,44,44,.16); border-color: rgba(244,44,44,.55); color: #f87171; }
.savings-cell-ahead{ background: rgba(59,130,246,.16); border-color: rgba(59,130,246,.55); color: #60a5fa; }
.savings-cell-inactive{ background: rgba(255,255,255,.02); border-color: var(--border); color: var(--muted); opacity: .6; }
.savings-legend{ display: flex; flex-wrap: wrap; gap: .9rem; font-size: .78rem; color: var(--muted); }
.savings-legend span{ display: inline-flex; align-items: center; gap: .35rem; }
.savings-dot{ width: 10px; height: 10px; border-radius: 50%; display: inline-block; }
.savings-dot-paid{ background: #4ade80; }
.savings-dot-late{ background: #f87171; }
.savings-dot-ahead{ background: #60a5fa; }
.savings-dot-inactive{ background: var(--muted); opacity: .6; }
/* Escritorio (≥768px): mismos datos, pero en 12 columnas (una por mes, de enero a diciembre de
   izquierda a derecha) para usar el ancho completo, con las semanas de cada mes apiladas verticalmente
   dentro de su columna — en mobile se conserva el layout vertical de arriba (una fila por mes). */
@media (min-width: 768px){
  .savings-heatmap{ display: grid; grid-template-columns: repeat(12, 1fr); align-items: start; gap: .5rem; }
  .savings-month-row{ flex-direction: column; align-items: stretch; gap: .4rem; }
  .savings-month-label{ width: auto; text-align: center; white-space: normal; }
  .savings-week-cells{ flex-direction: column; flex-wrap: nowrap; gap: .3rem; }
  .savings-week-cell{ width: auto; min-height: auto; padding: .3rem .15rem; }
}

/* Modal de felicitación de cumpleaños. */
.birthday-modal-content{
  background: radial-gradient(circle at 50% 0%, rgba(252,114,11,.16), transparent 60%), var(--card-detail, #12161f);
  border: 1px solid var(--border);
}
.birthday-modal-icon{ font-size: 4rem; line-height: 1; }
.birthday-modal-verse{ font-style: italic; font-size: .92rem; color: var(--muted); }
/* Modal de detalle de un premio de la Vitrina (#awardBadgeModal, ver detail.html/app.js). */
.award-badge-modal-icon{ font-size: 4rem; line-height: 1; }
/* Avatar(es) de quién ganó el premio ese año (ver awardWinnersHtml() en app.js) — borde del color
   de SU grupo, fijado por elemento vía --group-color (groupColorVar()), igual que el resto de
   avatares con borde de grupo de la app. */
.award-winners-avatars{ display: flex; justify-content: center; gap: 1rem; flex-wrap: wrap; }
.award-winner{ display: flex; flex-direction: column; align-items: center; gap: .3rem; }
.award-winner-avatar{
  width: 52px; height: 52px; border-radius: 50%; object-fit: cover;
  border: 2px solid var(--group-color, var(--ads));
}
.award-winner-name{ font-size: var(--fs-xs); font-weight: 700; }

/* Confeti: piezas <div> generadas por JS (ver launchConfetti() en app.js), cayendo con una
   animación CSS — sin depender de ninguna librería externa. */
.confetti-container{ position: fixed; inset: 0; pointer-events: none; z-index: 3000; overflow: hidden; }
.confetti-piece{
  position: absolute;
  top: -12px;
  width: 9px;
  height: 14px;
  opacity: .95;
  animation-name: confettiFall;
  animation-timing-function: ease-in;
  animation-fill-mode: forwards;
}
@keyframes confettiFall{
  0%{ transform: translateY(0) translateX(0) rotate(0deg); opacity: 1; }
  100%{ transform: translateY(105vh) translateX(var(--drift)) rotate(var(--rot)); opacity: .35; }
}
/* Variante con emoji en vez de rectángulo de color (ver launchConfetti(emoji) en app.js). */
.confetti-piece-emoji{
  width: auto;
  height: auto;
  font-size: 1.4rem;
  line-height: 1;
}
/* Confeti CONTENIDO dentro de una tarjeta (card-person / el detail-card del perfil) al hacer clic
   en el badge de cumpleaños — misma pieza .confetti-piece, pero recortada al propio contenedor en
   vez de cubrir toda la pantalla. Ver launchConfettiInside() en app.js. */
.confetti-target{ position: relative; overflow: hidden; }
.confetti-container-inline{ position: absolute; inset: 0; pointer-events: none; z-index: 5; overflow: hidden; }
/* top (no transform:translateY) porque % en top SÍ es relativo a la altura del contenedor
   (.confetti-container-inline, que cubre toda la tarjeta) — translateY en % es relativo al alto
   de la propia pieza (14px), por eso antes apenas se movía y el confeti se veía "atorado" arriba. */
.confetti-container-inline .confetti-piece{ top: -8%; animation-name: confettiFallInline; }
@keyframes confettiFallInline{
  0%{ top: -8%; transform: translateX(0) rotate(0deg); opacity: 1; }
  100%{ top: 105%; transform: translateX(var(--drift)) rotate(var(--rot)); opacity: .35; }
}
/* Badge de cumpleaños: redondo, clickeable, sobre el nombre/apodo de quien cumple años hoy. */
.birthday-cake-badge{
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  border: 1px solid rgba(252,114,11,.6);
  background: rgba(252,114,11,.18);
  font-size: 1rem;
  line-height: 1;
  cursor: pointer;
  padding: 0;
  animation: birthdayPulse 1.6s ease-in-out infinite;
}
@keyframes birthdayPulse{
  0%,100%{ box-shadow: 0 0 0 0 rgba(252,114,11,.45); }
  50%{ box-shadow: 0 0 0 6px rgba(252,114,11,0); }
}

/* ============ Introducción de primer login (bienvenida.php) ============
   Página completa (ya no modal) que sustituye a los antiguos #welcomeModal + #verifyDataModal:
   bienvenida + wizard de "Mi perfil" en modo obligatorio, sin botón de cerrar/omitir — solo
   Volver/Siguiente, con una barra de progreso y una barra de navegación fija abajo, al estilo de
   los "onboarding" de app. Ver initBienvenida() en app.js. */
/* Sin navbar en esta página (a propósito: bienvenida.php es un flujo obligatorio, sin salida ni
   distracciones — ni logo, ni cambio de tema, ni cerrar sesión). body como columna flex de 100dvh
   con .intro-wrap como único hijo y flex:1: así .intro-wrap siempre mide 100dvh completo, y
   .intro-card (stretch por default, cross-axis del flex de .intro-wrap) queda del 100% de esa
   altura, para que el paso actual (el que sea) se vea como una pantalla completa de "onboarding"
   en vez de una tarjeta chica flotando en medio. */
body[data-page="bienvenida"]{ padding-bottom: 5.5rem; min-height: 100dvh; display: flex; flex-direction: column; } /* padding-bottom deja libre el alto de .intro-footer-bar */
.intro-wrap{ position: relative; padding: 2.25rem 1rem 1rem; display: flex; justify-content: center; flex: 1; }
.intro-card{
  width: 100%; max-width: 900px;
  border-radius: var(--radius-xl);
  border: 1px solid var(--border);
  background: rgba(255,255,255,.04);
  box-shadow: var(--shadow);
  padding: 1.75rem 1.75rem 2rem;
}
[data-theme="light"] .intro-card{ background: var(--card); }
.intro-progress{ max-width: 420px; margin: 0 auto 1.75rem; }
.intro-progress-bar{ height: 6px; border-radius: 999px; background: rgba(255,255,255,.08); overflow: hidden; }
[data-theme="light"] .intro-progress-bar{ background: rgba(0,0,0,.08); }
.intro-progress-fill{
  height: 100%; width: 0; border-radius: 999px;
  background: linear-gradient(90deg, var(--group-color-1, var(--ads)), var(--group-color-2, var(--joven)), var(--group-color-3, var(--ads)));
  transition: width .35s ease;
}
.intro-progress-label{ margin-top: .55rem; text-align: center; font-size: var(--fs-xs); color: var(--muted); font-weight: 800; letter-spacing: .045em; text-transform: uppercase; }
#introWelcomeStep{ max-width: 640px; margin: 1.5rem auto; }
#introWelcomeStep .birthday-modal-icon{ font-size: 3.5rem; }
/* Avatar de quien inició sesión en el paso 0 de bienvenida.php — borde del color de SU grupo
   (--group-color por elemento, ver fillIntroWelcome() en app.js), mismo criterio que el resto de
   avatares con borde de grupo de la app (person-badge, card-person, etc.). */
.intro-avatar{
  width: 96px; height: 96px; border-radius: 50%; object-fit: cover;
  border: 3px solid var(--group-color, var(--ads));
  margin: 0 auto; display: block;
  background: rgba(255,255,255,.04);
}
/* Las 4 tarjetitas de "En esta aplicación encontrarás" — mismo espíritu que .summary-bar-stat
   (ícono en círculo + etiqueta), pero centradas y sin valor numérico. */
.intro-feature{
  display: flex; flex-direction: column; align-items: center; gap: .5rem;
  height: 100%; padding: 1rem .5rem;
  border-radius: var(--radius-sm);
  background: rgba(255,255,255,.04);
}
[data-theme="light"] .intro-feature{ background: rgba(0,0,0,.03); }
.intro-feature-icon{
  width: 44px; height: 44px; border-radius: 50%; flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
  font-size: 1.2rem; color: var(--ads);
  background: color-mix(in srgb, var(--ads) 16%, transparent);
}
.intro-feature-label{ font-size: var(--fs-sm); font-weight: 600; line-height: 1.2; }
.intro-step-title{ font-size: 1.5rem; font-weight: 800; margin-bottom: 3rem; gap: .5rem; text-align: center !important;}
/* Mismo nivel de transparencia que .navbar-glass.is-scrolled (línea ~94), para que la barra fija
   de abajo empate con el resto del "cristal" de la UI en vez de verse casi opaca. */
.intro-footer-bar{
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 1030;
  background: rgba(10,14,22,.55);
  backdrop-filter: blur(10px);
  border-top: 1px solid var(--border);
  padding: .85rem 1rem;
}
[data-theme="light"] .intro-footer-bar{ background: rgba(255,255,255,.70); }

[data-theme="light"] .text-white { color: #000000 !important;}

/* Resumen de asistencia / Resumen de tareas (Inicio, solo usuario): barra horizontal segmentada
   (una franja por categoría, ancho proporcional a su parte del total) + una tarjeta por categoría
   debajo (ícono + número + etiqueta) — ver summaryBarHtml() en app.js. El track es negro
   transparente por default (mismo tono en ambos temas): si no hay datos todavía, simplemente no
   se pinta ningún segmento y se ve esa franja vacía en vez de un color de relleno inventado. */
.summary-bar-track{
  display: flex;
  width: 100%;
  height: 14px;
  border-radius: 999px;
  overflow: hidden;
  background: rgba(0,0,0,.15);
}
.summary-bar-seg{ height: 100%; transition: width .4s ease; }
.summary-bar-legend{ margin-top: 1.1rem; }
.summary-bar-stat{
  display: flex; flex-direction: column; align-items: flex-start; text-align: left;
  gap: .35rem; height: 100%;
  padding: 1.1rem .5rem;
}
.summary-bar-stat-top{ display: flex; align-items: center; gap: .5rem; }
.summary-bar-stat-icon{
  width: 38px; height: 38px; flex-shrink: 0; border-radius: 50%;
  display: flex; align-items: center; justify-content: center; font-size: 1.05rem;
  background: color-mix(in srgb, currentColor 16%, transparent);
}
.summary-bar-stat-value{ font-size: 1.3rem; font-weight: 800; line-height: 1.1; }
.summary-bar-stat-label{ font-size: var(--fs-xs); color: var(--muted); }

/* Mini-KPIs dentro de cada tarjeta de integrante en "Mi equipo" (ver renderMiEquipoGrid() en
   app.js): las columnas que antes vivían en la tabla (%Asistencia, %Tareas, Pts. de asistencia,
   Pts. de tareas) — "Pos", "Integrante" y "Puntos" totales ya están representados por la medalla,
   el nombre y el renglón de "N° lugar • X pts" de la propia tarjeta. */
.mini-kpi-grid{ display: grid; grid-template-columns: 1fr 1fr; gap: .4rem; margin-top: .75rem; }
.mini-kpi{ background: rgba(255,255,255,.05); border-radius: var(--radius-sm); padding: .4rem .6rem; }
[data-theme="light"] .mini-kpi{ background: rgba(0,0,0,.04); }
.mini-kpi-value{ font-weight: 800; font-size: .95rem; line-height: 1.15; }
.mini-kpi-label{ font-size: var(--fs-xs); color: var(--muted); }

/* Variante con ícono (ver p.ej. la tarjeta "Estadísticas de X" del detalle de un discípulo, en
   renderDetail() en app.js): mismo círculo de color que .summary-bar-stat-icon, pero estirado
   (align-items:stretch) para abarcar el alto completo de valor+etiqueta en vez de solo el del
   valor — un ícono "de cuerpo entero" junto al bloque de texto, no uno chico en línea con la
   etiqueta. Color: --group-color (fijado por elemento vía groupColorVar(), ver renderDetail() en
   app.js) — el del GRUPO al que pertenece la persona que se muestra, no el de quien mira la
   página (por eso NO usa --group-color-1/2/3, que son del usuario logueado). */
.mini-kpi-with-icon{ display: flex; align-items: stretch; gap: .6rem; }
.mini-kpi-icon{
  flex-shrink: 0;
  width: 2.3rem;
  border-radius: var(--radius-sm);
  display: flex; align-items: center; justify-content: center;
  font-size: 1.05rem;
  color: var(--group-color, var(--ads));
  background: color-mix(in srgb, currentColor 16%, transparent);
}
.mini-kpi-body{ display: flex; flex-direction: column; justify-content: center; min-width: 0; }
.mini-kpi-body .mini-kpi-value,
.mini-kpi-body .mini-kpi-label{ margin: 0; }

/* Componente "quote": frase o versículo destacado (p.ej. la frase de Michael Jordan y el pasaje
   bíblico de "Mi equipo"), con una comilla grande decorativa y una franja de color a la izquierda
   — reutilizable en cualquier sección que necesite una cita. */
.quote{
  position: relative;
  background: rgba(255,255,255,.05);
  border: 1px solid var(--border);
  border-left: 3px solid var(--group-color-1, var(--ads));
  border-radius: var(--radius-sm);
  padding: 1.1rem 1.25rem 1.1rem 2.5rem;
}
[data-theme="light"] .quote{ background: rgba(0,0,0,.03); }
.quote.text-center{ padding-left: 1.25rem; }
.quote::before{
  content: "\201C";
  position: absolute;
  left: .6rem; top: -.15rem;
  font-size: 2.6rem;
  line-height: 1;
  font-family: Georgia, serif;
  color: var(--group-color-1, var(--ads));
  opacity: .5;
}
.quote.text-center::before{ display: none; }
.quote-text{ font-style: italic; margin: 0 0 .35rem; }
.quote-cite{ display: block; color: var(--muted); font-size: var(--fs-xs); font-style: normal; }
.quote:not(.text-center) .quote-cite{ text-align: right; }
.quote + .quote{ margin-top: .6rem; }

/* Tarjeta de horario de reunión (/horarios.php, ver horarioCardHtml() en app.js) — pintada del
   color del grupo al que aplica, con el mismo efecto de elevación al hover que .card-person.
   Estructura: badge de grupo arriba a la izquierda, ícono en su propio badge centrado, título,
   resumen y una fila de mini-kpis propia (.horario-kpi-*, ver más abajo — deliberadamente NO
   reutiliza .mini-kpi para no afectarlo con el orden label-arriba/valor-abajo que aquí se pide). */
.horario-card{
  position: relative;
  background: linear-gradient(160deg, color-mix(in srgb, var(--group-color, var(--ads)) 16%, transparent), transparent);
  border: 1px solid color-mix(in srgb, var(--group-color, var(--ads)) 55%, transparent);
  border-radius: var(--radius);
  padding: 1.1rem 1.15rem;
  transition: transform .2s ease, box-shadow .2s ease, border-color .2s ease;
}
.horario-card:hover{
  transform: translateY(-4px);
  border-color: var(--group-color, var(--ads));
  box-shadow: var(--shadow-hover);
}
.horario-card-group-badge{ position: absolute; left: .9rem; top: .9rem; }
.horario-card-icon-wrap{ text-align: center; margin-top: 1.6rem; }
.horario-card-icon-badge{
  display: inline-flex; align-items: center; justify-content: center;
  width: 3rem; height: 3rem;
  border-radius: 50%;
  font-size: 1.5rem;
  background: color-mix(in srgb, var(--group-color, var(--ads)) 22%, transparent);
  border: 1px solid color-mix(in srgb, var(--group-color, var(--ads)) 55%, transparent);
}
.horario-card-title{ font-weight: 800; font-size: 1.05rem; text-align: center; margin-top: .6rem; }
/* min-height fija (≈3 líneas): para que todas las tarjetas de horario midan lo mismo sin importar
   si su resumen es más corto o más largo que el de las demás. */
.horario-card-body{ color: var(--muted); font-size: .85rem; line-height: 1.4; text-align: center; margin: .5rem 0 0; min-height: 3.6rem; }

/* Mini-kpi propio de la tarjeta de horario: mismo look que .mini-kpi (fondo sutil + radio chico)
   pero con el label ARRIBA y el valor abajo — clase separada a propósito para no tocar .mini-kpi,
   que se usa en varias otras secciones (Mi equipo, detalle de discípulo, Mi iglesia) con el orden
   valor-arriba/label-abajo. */
.horario-kpi-grid{ display: grid; grid-template-columns: 1fr 1fr; gap: .4rem; margin-top: .9rem; }
.horario-kpi{ background: rgba(255,255,255,.05); border-radius: var(--radius-sm); padding: .4rem .6rem; }
[data-theme="light"] .horario-kpi{ background: rgba(0,0,0,.04); }
.horario-kpi-label{ font-size: var(--fs-xs); color: var(--muted); }
.horario-kpi-value{ font-weight: 800; font-size: .95rem; line-height: 1.15; }

/* Ajustes específicos de iPad en horizontal (1024×768 real cae en el breakpoint `lg` de Bootstrap,
   992–1199.98px) — todo scoped por id/clase para no tocar mobile, iPad vertical (`md`, 768px) ni
   desktop real (`xl`, ≥1200px). */
@media (min-width: 992px) and (max-width: 1199.98px){
  /* 1. Top bar solo íconos: los mismos <span class="d-none d-md-inline"> que ya ocultan el texto
     en mobile y lo muestran desde `md` se vuelven a ocultar en este rango — ni el logo/badge de
     grupo ni el avatar/tema se tocan (ya son solo-ícono siempre). */
  #mainTabs .nav-item .btn-neon span,
  #navAccountBox > a.btn-neon span{ display: none !important; }

  /* 2. Tabs de detail.html en una sola fila con scroll horizontal — mismo tratamiento que ya usan
     las demás barras de sub-tabs en mobile (ver @media(max-width:767.98px) arriba), pero solo para
     #detailTabs: a este ancho las demás barras de sub-tabs ya caben bien en una fila. */
  #detailTabs{
    flex-wrap: nowrap !important;
    overflow-x: auto;
    overflow-y: hidden;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    -ms-overflow-style: none;
  }
  #detailTabs::-webkit-scrollbar{ display: none; height: 0; }
  #detailTabs > .nav-item{ flex: 0 0 auto; }

  /* 3. Directorio y Equipos a 3 cards por fila (en vez de las 4 de `col-lg-3`). */
  #grid > [class*="col-"],
  #teamsGrid > [class*="col-"]{ flex: 0 0 33.3333%; max-width: 33.3333%; }

  /* 4. Mini-KPI de la tarjeta "Estadísticas de X" del detalle a 12 columnas (una sola columna a
     todo el ancho) para que no se salga del detail-card angosto de la columna col-lg-4 — clase
     propia (.detail-stats-kpis) para no afectar .mini-kpi-grid en las tarjetas de "Mi equipo". */
  .detail-stats-kpis{ grid-template-columns: 1fr; }

  /* 5. KPIs de Tareas/Asistencias del detalle a col-4: con exactamente 3 tarjetas, el col-md-3 de
     renderKpiRow() deja un hueco vacío del 25% a la derecha — a #aulaTareasKpis/#aulaAsistenciaKpis
     (tareas.php/asistencia.php) no se les toca. */
  #detailTareasKpis .col-md-3,
  #detailAsistenciaKpis .col-md-3{ flex: 0 0 33.3333%; max-width: 33.3333%; }
}
/* Los efectos del directorio se componen solo dentro del viewport. La tarjeta
   sigue visible aunque el navegador no soporte IntersectionObserver. */
#grid .card-person{ opacity: 1; animation: none; transform: none; }
#grid .card-person.is-visible.fade-in{ animation: directory-enter .55s ease backwards; }
@keyframes directory-enter{ from{ opacity: 0; transform: translateY(10px); } to{ opacity: 1; transform: none; } }
#grid .card-person:not(.is-visible)::before,
#grid .card-person:not(.is-visible)::after{ content: none; }
#grid .card-person:not(.is-visible) .avatar{
  -webkit-mask-image: none;
  mask-image: none;
  transition: none;
}
#grid .card-photo-swap:not(.photo-ready) .avatar-main{ opacity: 1; }
#grid .card-photo-swap:not(.photo-ready) .avatar-hover{ opacity: 0; }
@media (prefers-reduced-motion: reduce){
  #grid .card-person.is-visible.fade-in{ animation: none; }
  #grid .avatar{ transition: none; }
}
