/* ═══════════════════════════════════════════════
   EQUIPO.CSS — Sección Equipo (Cambio 42, greenfield)
   Lista (#s-equipo) + perfil de detalle (#s-equipo-perfil).
   Solo variables de colors.css. Prefijo de clases: eq-
   ═══════════════════════════════════════════════ */

/* ── Estado de carga del roster real (Cambio 55, #eq-estado-carga,
   index.html) -- ver _eqInit()/js/equipo.js. */
.eq-loading, .eq-error { text-align: center; font-size: 0.85rem; color: var(--muted); padding: 32px 16px; }
.eq-error { color: var(--danger); }

/* Cursor parpadeante del buscador (pedido explícito) -- la regla global
   `input[placeholder*="uscar" i]` (css/ui.css) NO alcanza a este input: su
   placeholder es "Busca en el equipo" (imperativo, sin la "r" de "buscar"),
   no contiene el substring "uscar" que esa regla matchea -- por eso el
   caret seguía parpadeando acá cuando en Eventos/Tareas (placeholders con
   "Buscar...") ya no. Fix puntual, scoped a este input -- no toca la regla
   global ni el placeholder real (cambiar el texto para que matchee sería
   más frágil, dependería de que nadie lo retoque después). */
#eq-search-input { caret-color: transparent; }

/* Pill de categoría (termómetro y header del perfil de detalle) + pill del
   total de "PUNTOS" -- borde naranja, fondo naranja tenue, texto naranja;
   comparten declaración. Nombres `.eq-mis-stats-*` heredados del panel
   "Mis estadísticas" (eliminado, ver MANIFEST.md) -- siguen en uso por
   `_eqStatsContenidoHtml()`/`_eqPerfilContenidoHtml()`. */
.eq-mis-stats-rol-pill, .eq-mis-stats-total-pill {
  display: inline-flex; align-items: center; padding: 2px 10px; border-radius: 999px;
  font-size: 0.72rem; font-weight: 800; border: 1px solid var(--brand);
  background: var(--brand-light); color: var(--brand);
}
/* Separador "PUNTOS" con el total inline (rediseño compacto, pedido
   explícito) -- reemplaza la card `.eq-stat-card--full` de "Puntos
   totales" que vivía como una card más del grid. Línea-label-pill-línea,
   mismo layout general que `.eq-grupo-header` (headers de acordeón de la
   lista, más abajo en este archivo) -- `.eq-grupo-linea`/`.eq-grupo-nombre`
   reusadas tal cual (mismo look, sin duplicar esas 2 reglas). */
.eq-mis-stats-puntos-row { display: flex; align-items: center; gap: 8px; width: 100%; margin: 14px 0 2px; }
/* Card combinada "Asistencia + Racha" (rediseño compacto, pedido explícito
   -- "en lugar de 2 cards separadas, mostrar una sola") -- 2 íconos juntos
   arriba (`sports`/asistencia + `local_fire_department`/racha), valor
   principal (asistencia) + badge de racha inline al lado (SOLO si hay
   puntos de racha ese período, `_eqStatsContenidoHtml()`/js/equipo.js
   -- mismo criterio "nunca 0" que el resto de esta sección), label único
   abajo. `.eq-stat-combo-racha-badge` es su PROPIO target de tap
   (`event.stopPropagation()`, ver ese comentario en js/equipo.js) -- mismo
   estilo naranja tenue que el resto de pills de esta sección, sin repetir
   la declaración completa (distinto padding/tamaño, chico a propósito
   para caber inline junto al valor grande). */
.eq-stat-card--combo { display: flex; flex-direction: column; align-items: center; }
.eq-stat-combo-iconos { display: flex; align-items: center; justify-content: center; gap: 4px; margin-bottom: 4px; }
.eq-stat-combo-iconos .material-symbols-rounded { font-size: 1.15rem; color: var(--brand); }
.eq-stat-combo-valor-row { display: flex; align-items: center; justify-content: center; gap: 6px; }
.eq-stat-combo-racha-badge {
  display: inline-flex; align-items: center; gap: 1px; padding: 1px 7px; border-radius: 999px;
  font-size: 0.68rem; font-weight: 800; background: var(--brand-light); color: var(--brand);
  cursor: pointer; -webkit-tap-highlight-color: transparent;
}
.eq-stat-combo-racha-badge .material-symbols-rounded { font-size: 0.8rem; }

/* ── Header sticky nuevo (rediseño, ver MANIFEST.md -- "colapso progresivo
   al scrollear"): antes `#eq-search-header` (la fila de íconos) era el
   único elemento sticky (`.app-nav.app-nav-sticky`, css/nav.css) y los 2
   paneles deslizables vivían AFUERA, en flujo normal -- al abrirse solo
   empujaban el contenido de abajo, sin quedar pegados arriba de nada.
   `.eq-sticky-header` es el wrapper sticky nuevo que envuelve la fila de
   íconos Y los 2 paneles (index.html) -- mismo criterio exacto que
   `.ev-sticky-header`/css/eventos.css (contiene `#ev-mes-panel`/
   `#ev-busqueda-panel`): con los paneles DENTRO del contenedor sticky, al
   abrirse quedan pegados arriba por encima del roster que scrollea detrás
   (`#eq-lista-contenido`, ver más abajo), en vez de solo desplazarlo.
   Mismo bleed -16px/+32px que tenía `.app-nav-sticky` (conserva el ancho de
   siempre) + mismo z-index:100 que ya usaba esa clase -- Eventos usa un
   z-index más bajo (20) porque `.ev-sticky-header` no compite con ningún
   otro elemento bleed a los costados; acá sí, así que se mantiene el valor
   que ya funcionaba. `.eq-header-row` es la fila de íconos en sí (antes el
   `.app-nav` completo aportaba layout+padding+fondo; ahora ese padding/fondo
   los da el wrapper, `.eq-header-row` solo aporta el layout flex, copiado de
   `.ev-header-row`/css/eventos.css). */
.eq-sticky-header {
  position: sticky; top: 0; z-index: 100; background: var(--bg);
  margin: 0 -16px 0 -16px; width: calc(100% + 32px); box-sizing: border-box;
  /* `padding-bottom:14px` (ver MANIFEST.md/CHANGELOG.md -- "gap entre nav y
     paneles expandidos") -- mismo valor EXACTO que `.ev-sticky-header`/
     css/eventos.css (`padding-bottom:14px`, separa el wrapper entero del
     roster/timeline que scrollea debajo, sin importar si algún panel está
     abierto). Bug real corregido: la 1ra versión de este wrapper (ver
     CHANGELOG.md, "colapso progresivo al scrollear") copió a mano el gap
     que tenía antes `.app-nav-sticky` (38px, `padding:22px`+`margin-bottom:16px`
     -- ver `.eq-header-row` de abajo) sin darse cuenta de que Eventos usa
     un gap mucho más chico (18px = este `padding-bottom:14px` + el
     `margin-bottom:4px` de `.eq-header-row`) -- con los paneles ahora
     DENTRO del wrapper (a diferencia de cuando `.app-nav-sticky` se copió),
     ese sobrante de 20px quedaba como aire muerto entre los íconos y el
     contenido real de cualquier panel abierto. */
  padding: 22px 16px 14px; animation: smoothSlideDown 0.4s var(--ease-sheet);
}
/* `margin-bottom:4px` (no `padding-bottom`) -- mismo valor EXACTO que
   `.ev-header-row`/css/eventos.css. Vive acá (no en `.eq-sticky-header`)
   por el mismo motivo documentado ahí: tiene que aplicar ENTRE la fila de
   íconos y el primer panel, no después de los 2 (`padding-bottom` del
   wrapper, arriba, es el que corre DESPUÉS de ambos, siempre). */
.eq-header-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; min-height: 56px; margin-bottom: 4px; }
/* ── Panel deslizable de la nav: filtros (Puntos / Rol)
   (rediseño, ver MANIFEST.md/CHANGELOG.md) -- mismo mecanismo de animación
   que `.ev-header-burbuja`/css/eventos.css (botón de búsqueda de Eventos):
   max-height animado + `.abierta`, JS idéntico en `_eqTogglePanel()`/
   js/equipo.js (lock del alto real antes de cerrar + doble
   requestAnimationFrame) -- CSS propio en vez de reusar la clase de eventos.css literal,
   mismo criterio de "cada sección mantiene su propio archivo" ya
   establecido en el resto de la app. El contenido interno vive en
   `.eq-filtros-panel-inner`. (El panel "Mis estadísticas" y su colapso
   progresivo al scrollear se eliminaron, ver MANIFEST.md.) */
.eq-filtro-toggle-btn.activo { background: var(--brand); color: var(--white); }
/* Badge numérico del botón de filtros (`tune`; feat nueva, ver MANIFEST.md -- "igual a
   como funciona en Eventos") -- COPIADO 1:1 de `.ev-header-icon-btn`/
   `.ev-filtro-badge` (css/eventos.css): `position:relative` ancla el badge
   (único aporte propio acá, igual que en Eventos -- tamaño/fondo/color/hover
   del botón en sí vienen de `.app-nav-icon-btn`, css/nav.css, sin tocar).
   Cuenta real en `_eqActualizarBadgeFiltros()`/js/equipo.js (solo filtros,
   nunca el texto de búsqueda). */
.eq-filtro-toggle-btn { position: relative; }
.eq-filtro-badge {
  position: absolute; top: -4px; right: -4px; min-width: 16px; height: 16px; padding: 0 3px;
  border-radius: 999px; background: var(--danger); color: var(--white);
  font-size: 0.62rem; font-weight: 800; display: flex; align-items: center; justify-content: center;
  line-height: 1; box-shadow: 0 0 0 2px var(--card-bg);
}
/* `background: var(--bg)` (bug real corregido, ver MANIFEST.md/CHANGELOG.md
   -- "borde del panel con contenido de la lista transparentándose por
   debajo"): antes este contenedor (el que de verdad recorta el contenido,
   `overflow:hidden`, durante el colapso progresivo al scrollear -- ver
   arriba) no pintaba NINGÚN fondo propio -- solo
   `.eq-filtros-panel-inner` (más abajo) lo hace, con un `margin-top:10px`
   antes de esa tarjeta. En flujo normal ese hueco de 10px queda cubierto
   por `background:var(--bg)` de `.eq-sticky-header` (su padre, ver arriba
   en este archivo) -- pero durante la animación el
   recorte puede caer justo en ese hueco sin fondo propio, dejando ver una
   franja del roster que sigue scrolleando detrás -- exactamente el "borde
   transparentándose" reportado. Fondo opaco DIRECTO en el contenedor que
   recorta, sin depender de ningún padre/hijo, elimina la franja pase lo
   que pase con la clip-line en cualquier instante de la animación. */
/* Perf real (pedido explícito, jank en Android Chrome al colapsar) --
   `max-height`/`height` siguen disparando layout en cada frame igual (son
   propiedades de box model, ninguna es "solo GPU") -- el cambio real de
   compositor-only es el `transform` de `.eq-panel-inner-anim` (hijos
   directos de este panel, ver `_eqAnimarPanel()`/js/equipo.js): el
   contenido se desliza vía `translateY` mientras el wrapper recorta con
   `height`, en vez de que TODO el trabajo visual dependa de reflow. `height`
   en vez de `max-height` -- valor numérico real medido (`scrollHeight`) en
   los 2 extremos de la transición, nunca infinito/auto, así que el cambio
   de propiedad no altera el comportamiento, solo separa el nombre de la
   propiedad para poder documentarla aparte de `max-height` (que sigue
   viviendo en otros paneles de esta sección sin este fix, ver
   `.eq-modal-centro-body`/etc., fuera de alcance de este pedido). `will-change`
   NO fijo acá -- se agrega/saca como clase temporal
   (`.eq-panel-wrapper-anim`) solo mientras dura la transición real
   (`_eqAnimarPanel()`), nunca permanente (costo de memoria de GPU si
   quedara siempre activo sin necesidad). */
/* Pulido posterior (pedido explícito, "sigue cortándose en Android
   Chrome"): 350ms (antes 260ms) + easing `cubic-bezier(0.25,0.46,0.45,0.94)`
   (entrada más suave, antes `cubic-bezier(0.4,0,0.2,1)`). `translateZ(0)`
   PERMANENTE (no atado a `.eq-panel-wrapper-anim`/`.eq-panel-inner-anim`,
   que siguen existiendo solo para `will-change`) -- promueve estos 2
   elementos a su propia capa de compositing desde que la página carga, no
   recién cuando arranca la transición: crear la capa a mitad de la
   animación (lo que pasaba antes, sin esto) puede generar el mismo tranco
   que se busca evitar. */
/* Easing -- `ease` a secas (pedido explícito #15, "unificar animación entre
   Equipo y Eventos"): reemplaza el `cubic-bezier(0.25,0.46,0.45,0.94)`
   custom ("entrada más suave") de la ronda de pulido anterior -- mismo
   valor exacto en `.ev-header-burbuja`/css/eventos.css. */
.eq-header-panel { height: 0; overflow: hidden; background: var(--bg); transition: height 350ms ease; transform: translateZ(0); }
.eq-header-panel.abierta { margin-bottom: 18px; }
.eq-header-panel.eq-panel-wrapper-anim { will-change: height; }
/* Estado "expandido de verdad" (pedido explícito, fix más robusto que medir
   `scrollHeight` al inicializar -- ver `_eqAbrirPanel()`/js/equipo.js):
   `height:auto` se recalcula solo en cada layout real, nunca depende de
   una medición congelada en un momento en que el ancestro (`#s-equipo`)
   podía seguir `display:none`. `overflow:visible` -- ya no hay nada que
   recortar con el panel en su alto natural; vuelve a `hidden` (regla base)
   apenas se saca esta clase para animar un cierre real. */
.eq-header-panel.eq-panel-auto { height: auto; overflow: visible; }
.eq-filtros-panel-inner { transition: transform 350ms ease; transform: translateZ(0); }
.eq-filtros-panel-inner.eq-panel-inner-anim { will-change: transform; }
.eq-filtros-panel-inner {
  /* `margin-top:14px` + `margin-bottom:4px` de `.eq-header-row` = 18px de
     gap nav->panel (mismo valor que Eventos). */
  background: var(--surface-2); border-radius: 16px; padding: 16px;
  margin-top: 14px;
  /* Border/stroke (rediseño, ver MANIFEST.md -- "revisar cómo está
     estilizado `.ev-header-burbuja-panel` y aplicar el mismo border/stroke
     al `.eq-filtros-panel-inner`") -- mismo valor exacto que
     `.ev-header-burbuja-panel`/css/eventos.css (`1.5px solid
     var(--border-light)`), la única propiedad que le faltaba a este panel
     para quedar visualmente igual -- padding/radius/margin-top quedan como
     ya estaban (14px/14px/16px en Eventos vs. 16px/16px/10px acá, nunca
     pedido tocarlos, solo el borde). */
  border: 1.5px solid var(--border-light);
}

/* ── "Filtros" + pills de categoría Puntos/Rol (rediseño, ver MANIFEST.md
   -- "rediseñar completamente el panel de filtros... para que funcione
   igual que Eventos") -- reemplaza los 2 títulos de sección fijos
   "Filtrar puntos según periodo"/"Filtrar por rol" (`.eq-filtro-seccion`,
   ya sacada de acá y de index.html, sin más consumidores) por el mismo
   patrón de Eventos: subtítulo "Filtros" + 2 pills de categoría con
   chevron ("Puntos"/"Rol") que despliegan su propia burbuja con los
   sub-pills reales al tocarlos. Bloque entero COPIADO 1:1 de
   `.ev-filtros-label`/`.ev-filtros-row`/`.ev-filtro-trigger`/
   `.ev-filtro-activo`/`.ev-filtro-burbuja`/`.ev-filtro-burbuja-pills`
   (css/eventos.css, los triggers "Lugar"/"Tipo") -- pedido explícito
   ("copiar el estilo visual... para los nuevos pills 'Puntos' y 'Rol' en
   equipo.css"), no reusado cross-archivo pese a que css/eventos.css ya
   está cargado en esta misma SPA: cada sección es dueña de sus propias
   clases, mismo criterio que el resto de este archivo (ver `.eq-cal-fecha-year-*`
   más abajo, mismo patrón con el selector de fecha de nacimiento). Solo
   variables de `colors.css`, ninguna nueva -- las mismas que ya usa
   `.ev-filtro-trigger`. `.eq-filtro-burbuja.abierta` con techo FIJO
   (320px, sin medir `scrollHeight`) -- mismo criterio que la burbuja de
   Eventos: contenido chico y acotado (2 pills / 7 pills como mucho) que
   nunca se acerca a ese techo, no hace falta el patrón scrollHeight-medido
   del resto de acordeones de esta sección. MISMO comportamiento radio que
   Eventos (re-ajuste, ver MANIFEST.md -- una ronda anterior las dejaba
   independientes, revertido por pedido explícito: "'Puntos' y 'Rol' no
   pueden estar abiertos al mismo tiempo") -- `_eqToggleFiltroBurbuja()`/
   js/equipo.js abrir una cierra la otra si estaba abierta, un solo valor
   `_eqFiltroBurbujaAbierta` ('puntos'|'rol'|null), no un mapa de
   booleanos. */
.eq-filtros-row { display: flex; gap: 8px; margin-bottom: 4px; }
.eq-filtro-trigger {
  flex: 1; min-width: 0; display: flex; align-items: center; justify-content: space-between; gap: 4px;
  padding: 10px 6px 10px 12px; border-radius: 18px; border: 1.5px solid var(--border-light);
  background: var(--surface-light); color: var(--text-2); font-size: 0.78rem; font-weight: 700;
  cursor: pointer; font-family: inherit; transition: all 0.15s;
}
.eq-filtro-trigger-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.eq-filtro-trigger .material-symbols-outlined { font-size: 18px; color: var(--muted); flex-shrink: 0; }
.eq-filtro-trigger.eq-filtro-activo { border-color: var(--brand); color: var(--brand); background: var(--brand-lightest); }
.eq-filtro-trigger.eq-filtro-activo .material-symbols-outlined { color: var(--brand); }
.eq-filtro-burbuja { max-height: 0; overflow: hidden; transition: max-height 0.28s var(--ease-sheet); }
.eq-filtro-burbuja.abierta { max-height: 320px; margin-bottom: 18px; }
.eq-filtro-burbuja-pills { padding: 10px 2px 16px; }

/* ── Sub-pills dentro de las burbujas "Puntos"/"Rol" (Fecha/Histórico/roles)
   -- re-ajuste, ver MANIFEST.md: SIN override propio, a propósito. Una
   ronda anterior scopeaba acá un estilo "outlined" (borde+texto de marca,
   sin relleno) para `.eq-periodo-pills`/`#eq-filtro-rol-pills`, pero el
   pedido real era que estos sub-pills se vieran IGUAL a los sub-pills de
   Eventos ("Asamblea", "Eventos", etc. dentro de las burbujas Lugar/Tipo,
   `_evRenderFiltroBurbujaPills()`/js/eventos.js) -- y esos son `.aj-pill`/
   `.aj-pill.activa` 100% vanilla (confirmado: css/eventos.css no trae
   ningún override de color/borde para `.ev-filtro-burbuja-pills .aj-pill`,
   solo el padding de la burbuja en sí, ver `.eq-filtro-burbuja-pills`
   arriba). El look "outlined" era correcto para los TRIGGERS de categoría
   (`.eq-filtro-trigger`, que sí replican `.ev-filtro-trigger` -- ESE
   componente sí es outlined en Eventos), pero no para estos sub-pills --
   `.aj-pill.activa` (perfil.css, relleno sólido `--brand`+texto
   `--white`) es exactamente el naranja que ya usa cualquier sub-pill
   seleccionado en toda la app, sin necesitar ningún override local. El
   pill "Fecha" tampoco lleva chevron (re-ajuste, pedido explícito: "es un
   sub-pill, no un acordeón") -- vuelve a ser un `<span class="aj-pill">`
   liso, sin `.eq-pill-fecha`/ícono, igual que "Histórico" y los pills de
   rol. */

/* ── Modal centrada "Fecha" (rediseño, ver MANIFEST.md -- "convertir el
   bottom sheet del selector de fecha a una modal centrada tipo Google
   Calendar") -- reemplaza por completo el `.bsheet-overlay`/`.bsheet` que
   tenía antes (slide-up desde abajo, ancho tope 480px, alto variable según
   contenido) por un componente propio, `.eq-modal-centro-*`. Mismo patrón
   "siempre montada, `opacity:0`+`pointer-events:none` en reposo, clase
   `.visible` la revela" que YA usa `.eq-confirm-sheet-overlay`/
   `.eq-confirm-sheet` (más abajo en este archivo, el sheet de confirmación
   "dar acceso admin") -- reusado tal cual en vez de reinventar el
   mecanismo `display:none`+`setTimeout` de los bsheets: sin `display:none`
   de por medio, `_eqCalFechaRenderContenido()`/js/equipo.js puede medir
   `scrollHeight` en cualquier momento sin el bug de "medir con
   display:none da 0" documentado en la cabecera de js/equipo.js -- ya ni
   hace falta el guard `instant` de esa función para EVITAR ese bug (sigue
   existiendo, pero ahora solo para que el primer paint no haga un 2do fade
   redundante sobre el fade+scale de la modal entera, ver
   `_eqAbrirModalFecha()`). La card CENTRADA en el viewport (`position:fixed`
   + `inset:0` + `margin:auto` + ancho/alto fijos -- centra un elemento
   `fixed` con dimensiones explícitas sin necesitar un wrapper flex nuevo)
   con entrada fade+scale (pedido explícito: "aparece desde el centro, no
   desde abajo") en vez de slide-up. Alto FIJO ~520px con `max-height:90vh`
   como techo de seguridad en viewports chicos (pedido explícito: "altura
   fija suficiente para mostrar el calendario completo sin cortes") -- a
   diferencia de un bsheet (que crece con su contenido), acá el contenido
   SÍ puede necesitar más alto que eso en la práctica (un mes de 6 filas,
   ej. agosto arrancando en sábado, o la grilla Ene-Dic completa, ver
   `.eq-modal-centro-body` más abajo -- bug real corregido, "el calendario
   se corta al cambiar de mes"): `overflow-y:auto` ahí SÍ es el
   comportamiento esperado para esos casos, con `min-height:0` (mismo
   bloque) haciendo que ese scroll interno realmente se active en vez de
   que el body se desborde silenciosamente y `overflow:hidden` de esta
   card lo recorte. Estructura en 3 franjas fijas
   (`display:flex;flex-direction:column`): header (título + botón cerrar
   "×", `.eq-modal-centro-header`), body scrolleable (`.eq-modal-centro-body`,
   contiene el calendario -- ver el bloque siguiente, "Modal calendario
   'Fecha'", sin cambios de esa lógica en esta ronda) y footer fijo
   (`.eq-modal-centro-footer`, reusa `.eq-confirm-sheet-btns`/
   `.eq-confirm-btn-cancel`/`.eq-confirm-btn-ok`, más abajo en este archivo
   -- MISMO componente 2-botones que ya usa `#eq-sheet-confirm-admin`/
   index.html, sin inventar un botón nuevo -- pedido explícito, "cerrarse al
   tocar el overlay o el botón Cancelar": antes el único botón del sheet era
   "Aplicar", sin ningún "Cancelar" explícito -- el cierre dependía de la
   manija/drag, que esta modal ya no tiene). `shared/bsheet.js`
   (drag-to-close de la manija) queda automáticamente fuera de juego -- ese
   listener delegado busca un `.bsheet` ancestro real, que esta card ya no
   tiene. */
.eq-modal-centro-overlay {
  position: fixed; inset: 0; z-index: 9700;
  background: var(--overlay-dark); opacity: 0; pointer-events: none;
  transition: opacity 0.28s ease;
}
.eq-modal-centro-overlay.visible { opacity: 1; pointer-events: all; }
.eq-modal-centro-card {
  position: fixed; z-index: 9701; inset: 0; margin: auto;
  width: min(400px, 92vw); height: min(520px, 90vh);
  display: flex; flex-direction: column; background: var(--bg); border: 1px solid var(--border-light);
  border-radius: 20px; overflow: hidden;
  opacity: 0; transform: scale(0.92); pointer-events: none;
  transition: opacity 0.28s ease, transform 0.28s cubic-bezier(0.16,1,0.3,1);
}
.eq-modal-centro-card.visible { opacity: 1; transform: scale(1); pointer-events: all; }
.eq-modal-centro-header {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 16px 8px 12px 20px; flex-shrink: 0; border-bottom: 1px solid var(--border-light);
}
.eq-modal-centro-titulo { font-size: 1rem; font-weight: 800; color: var(--text); }
.eq-modal-centro-cerrar {
  display: flex; align-items: center; justify-content: center; flex-shrink: 0;
  width: 36px; height: 36px; border-radius: 50%; background: none; border: none;
  color: var(--muted); cursor: pointer; -webkit-tap-highlight-color: transparent;
}
@media (hover: hover) { .eq-modal-centro-cerrar:hover { background: var(--brand-lightest); color: var(--text); } }
/* `min-height: 0` (bug real corregido, ver MANIFEST.md -- "el calendario se
   corta al cambiar de mes"/"últimos meses del selector cortados") -- un
   `flex-item` sin `min-height` propio tiene `min-height:auto` por default
   (gotcha clásico de flexbox), que lo deja CRECER más allá del alto
   disponible en vez de respetar `flex:1` y activar su propio
   `overflow-y:auto` -- con un mes de 6 filas (ej. agosto, que arranca en
   sábado) o la grilla Ene-Dic completa, el body terminaba más alto que el
   espacio real que le quedaba dentro de `.eq-modal-centro-card` (alto FIJO,
   ver arriba), y el `overflow:hidden` de esa card (ahí solo para las
   esquinas redondeadas) recortaba silenciosamente las últimas filas en vez
   de dejarlas scrolleables. `min-height:0` fuerza al body a respetar de
   verdad el alto que le asigna `flex:1`, activando su `overflow-y:auto`
   real -- cualquier mes/grilla que no entre entero queda scrolleable
   adentro del body, nunca cortado sin forma de llegar a él. */
.eq-modal-centro-body { flex: 1; min-height: 0; overflow-y: auto; padding: 16px 16px 24px; }
.eq-modal-centro-footer { padding: 14px 16px calc(14px + env(safe-area-inset-bottom, 0px)); flex-shrink: 0; border-top: 1px solid var(--border-light); }

/* ── Modal calendario "Fecha" (contenido interno, sin cambios de esta ronda
   -- ver el bloque de arriba, "Modal centrada 'Fecha'", para el chrome
   exterior nuevo) ────────────────────────────────────────────────────────
   3 vistas dentro del mismo sheet -- días/meses/años, ver el comentario
   grande en js/equipo.js ("Modal 'Elige la fecha'") para el detalle
   completo. El header (`.ev-ant-cal-nav*`), la grilla de días
   (`.ev-cal-grid`/`.ev-cal-dow`/`.ev-cal-celda`/`.ev-cal-num`), la grilla de
   meses (`.ev-ant-mes-grid`/`.ev-ant-mes-cell`) y el resumen
   (`.ev-ant-rango-*`) son TODOS de css/eventos.css, reusados 1:1 sin una
   sola declaración duplicada acá -- ver `#eq-modal-fecha-*` en index.html y
   `_eqRenderModalFecha()`/js/equipo.js. Lo genuinamente nuevo acá es:
   (1) `.eq-cal-fecha-header-labels`, el wrapper de las 2 PALABRAS tappeables
   del header (mes/año, pedido explícito re-ajuste: "header separado en 2
   partes tappeables... deben verse visualmente distintas") -- cada palabra
   con su propio subrayado para diferenciarse entre sí Y del resto de usos
   de `.ev-ant-cal-nav-label` en la app (texto pelado, no interactivo):
   punteado + color de marca para el mes, sólido + color de texto normal
   para el año; (2) `.eq-cal-fecha-year-*`, la grilla de años -- mismo
   patrón visual que `.dp-year-grid`/`.dp-year-btn`
   (`shared/date-picker.js`+`inscripcion/inscripcion.css`, el selector de
   fecha de nacimiento -- 3 columnas, año actual/mostrado resaltado, scroll
   vertical, pedido explícito "estilo Google Calendar") DUPLICADO acá en vez
   de importado: ese CSS vive en `inscripcion/inscripcion.css`, que esta SPA
   principal no carga. `.hoy` (borde, sin relleno) marca el año calendario
   REAL de hoy -- separado de `.activo` (relleno de marca), que marca el año
   actualmente MOSTRADO en la grilla de días -- mismo criterio dual que
   `.ev-ant-cal-hoy`/`.ev-ant-cal-sel` de la grilla de días; (3)
   `.eq-cal-fecha-viewport`, el wrapper que anima el RESIZE del bottom sheet
   al cambiar de mes/vista (pedido explícito re-ajuste: "no debe
   expandirse/contraerse abruptamente") -- `overflow:hidden` +
   `transition:max-height` clásico de esta sección (mismo patrón que
   `.eq-header-panel`, más arriba en este archivo),
   `_eqCalFechaRenderContenido()`/js/equipo.js es quien anima el valor real
   en cada cambio (fija el alto ACTUAL antes de pintar, mide el alto NUEVO
   recién pintado, deja que esta `transition` interpole entre los 2) EN
   PARALELO al fade del contenido (`_evFadeSwap()`/js/eventos.js, reusada
   tal cual -- sin duplicar esa lógica acá). */
.eq-cal-fecha-header-labels { display: flex; align-items: center; gap: 6px; }
.eq-cal-fecha-label { cursor: pointer; -webkit-tap-highlight-color: transparent; text-underline-offset: 4px; }
.eq-cal-fecha-label-mes { color: var(--brand); text-decoration: underline dotted; }
.eq-cal-fecha-label-anio { color: var(--text); text-decoration: underline solid; }
.eq-cal-fecha-viewport { overflow: hidden; transition: max-height 0.32s ease; }
.eq-cal-fecha-year-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; max-height: 320px; overflow-y: auto; padding: 4px 0; }
.eq-cal-fecha-year-btn {
  padding: 14px 4px; border-radius: 14px; border: 1.5px solid transparent; font-size: 0.95rem; font-weight: 600;
  color: var(--text); background: var(--surface-2); cursor: pointer; text-align: center;
  -webkit-tap-highlight-color: transparent; font-family: inherit; transition: background 0.15s, color 0.15s, border-color 0.15s;
}
.eq-cal-fecha-year-btn.hoy:not(.activo) { border-color: var(--brand); color: var(--brand); }
.eq-cal-fecha-year-btn.activo { background: var(--brand); color: var(--white); border-color: var(--brand); font-weight: 700; }
/* Fechas futuras deshabilitadas en el selector de fecha (ver MANIFEST.md --
   "días/meses futuros no seleccionables") -- mismo criterio visual EXACTO
   que `.ev-ant-cal-pasado`/css/eventos.css (asistencia anticipada, que
   deshabilita fechas PASADAS -- acá es al revés, fechas FUTURAS, pero el
   tratamiento visual es idéntico: `opacity`+`pointer-events:none`+
   `cursor:not-allowed`) -- clase propia en vez de reusar esa, mismo criterio
   de "cada sección es dueña de sus propias clases" ya establecido en el
   resto de este archivo. Se suma tanto a celdas de día (`.eq-cal-fecha-futuro`
   junto a `.ev-cal-celda`, `_eqPintarDiasGrid()`) como a botones de mes
   (junto a `.ev-ant-mes-cell`, `_eqPintarMesesGrid()`) -- en ambos casos el
   `onclick` real también se omite del HTML generado (no alcanza con el
   `pointer-events` solo, mismo criterio que `_evAntCalRender()`: un
   `onclick` inline sigue existiendo aunque `pointer-events` lo bloquee al
   mouse/touch, y algo como un toque físico rápido puede colarse antes de
   que el navegador aplique el estilo). */
.eq-cal-fecha-futuro { opacity: 0.32; pointer-events: none; cursor: not-allowed; }
.eq-cal-fecha-futuro .ev-cal-num { color: var(--muted); }

/* ── Favoritos ────────────────────────────────── */
/* Rediseño: Favoritos ahora es un `.eq-grupo` idéntico a Quindes/Mirlxs/
   Inactivos (hereda TODOS sus estilos de ahí -- header, pill, líneas,
   body, ver "Grupos colapsables" más abajo -- sin duplicar nada). Lo único
   específico es la transición de `max-height` + opacidad para el slide+fade
   de la SECCIÓN completa al agregar/sacar el primer/último favorito
   (`_eqAnimarCambioFavorito()`/`_eqMostrarSeccionFavoritos()`/
   `_eqOcultarSeccionFavoritos()`, js/equipo.js) -- no colapsable, nunca
   pasa por `_eqToggleGrupo()`. `max-height`/`overflow` (pedido explícito:
   "debe deslizarse suavemente hacia abajo... no debe aparecer
   abruptamente" -- antes solo animaba opacity, con `display:block` dando
   el alto final de un salto en el mismo frame) ~300ms, misma curva `ease`
   que la opacidad para que ambas terminen juntas. */
.eq-favoritos-wrap { overflow: hidden; transition: opacity 0.3s ease, max-height 0.3s ease; }
/* `.eq-favoritos-vacio` sigue viva -- NO es exclusiva de Favoritos (que ya
   no la usa, ver arriba): la reusan el empty state de "sin rol asignado"
   (`_eqRenderPorRol()`) y `#eq-mes-vacio` (búsqueda por mes de cumpleaños,
   index.html), ambos en este mismo archivo. */
.eq-favoritos-vacio {
  border: 1.5px dashed var(--border-mid); border-radius: 14px;
  padding: 28px 16px; text-align: center; color: var(--muted); font-size: 0.82rem;
}
.eq-favoritos-vacio .material-symbols-outlined {
  display: block; font-size: 2rem; color: var(--brand-30); margin-bottom: 8px;
}

/* ── Grupos colapsables (Quindes / Mirlxs) ───────
   Header: línea a cada lado + pill de conteo + nombre + chevron -- botón
   real (no solo texto) para que todo el ancho sea tocable, no solo el
   nombre. Body con max-height animado, mismo mecanismo que el resto de
   acordeones de la app (ver css/eventos.css .ev-ant-acc-body). */
.eq-grupo { margin-bottom: 6px; }
.eq-grupo-header {
  display: flex; align-items: center; gap: 10px; width: 100%;
  background: none; border: none; padding: 10px 0; margin: 0;
  cursor: pointer; font-family: inherit; -webkit-tap-highlight-color: transparent;
}
/* Headers sticky apilados (rediseño REAL, ver MANIFEST.md/CHANGELOG.md --
   "los sticky headers deben apilarse, no reemplazarse") -- `position:sticky`
   nativo (versión anterior de esta feature) NO ALCANZA para un apilado que
   sobreviva más allá de la sección propia: un elemento `position:sticky`
   solo puede quedar pegado mientras su PROPIO contenedor (`.eq-grupo`/
   `#eq-favoritos-wrap`, uno por sección) todavía tiene alto por scrollear
   debajo -- en cuanto esa sección entera pasa, el navegador lo suelta y
   sigue de largo con el resto del scroll (bug real: "aparece Mirlxs,
   desaparece Quindes" en vez de acumularse). Fix: el pegado ahora lo maneja
   `_eqActualizarStickyHeaders()`/js/equipo.js A MANO, alternando cada
   header entre `.eq-grupo-header--sticky` (marcador, SIN estilo propio --
   solo distingue estos 5 headers reales de los `.eq-grupo-header` al vuelo
   de la vista "por rol", `_eqRenderPorRol()`, que nunca deben stickear) y
   este modificador nuevo:
   - `.eq-grupo-header--stuck` (JS la agrega/saca en cada pasada de scroll):
     `position:fixed` con `top`/`left`/`width` inline (JS, ver
     js/equipo.js) -- flota pegado arriba en CUALQUIER punto del scroll
     posterior a su sección, no solo dentro de ella, así se acumula con los
     headers stuck anteriores en vez de desaparecer con ellos.
   `background: var(--bg)` (mismo valor que `.eq-sticky-header`/`.pantalla`,
   no un color nuevo) -- opaco a propósito (ver MANIFEST.md -- "las cards de
   personas se ven a través del sticky header"): sin esto, el roster que
   sigue scrolleando detrás se vería a través del header apilado. `z-index:5`
   -- por encima del roster (sin z-index propio) pero bien por debajo de
   `.eq-sticky-header` (`z-index:100`, así nunca queda tapando la nav si sus
   alturas llegaran a coincidir) -- suficiente para cubrir del todo el
   contenido que pasa por debajo, headers stuck nunca se superponen ENTRE
   sí (sus `top` se calculan para quedar exactamente apilados, sin overlap).
   `.eq-grupo-header-spacer` (nuevo `<div>` vacío, index.html, uno por
   header, JUSTO ANTES de él dentro del mismo `.eq-grupo`/
   `#eq-favoritos-wrap`) compensa el espacio que el header deja de aportar
   al flujo del documento apenas pasa a `position:fixed` (que lo saca del
   flujo por completo) -- 0px mientras el header está NATURAL (no hace
   falta, el header ya reserva su propio lugar), `header.offsetHeight`px
   mientras está STUCK (ver js/equipo.js) -- sin este spacer, el resto del
   contenido saltaría hacia arriba el alto exacto del header apenas se
   vuelve stuck. */
.eq-grupo-header-spacer { height: 0; }
/* `transition:opacity` vive en la regla BASE (no solo en `.eq-panel-abierto`
   de abajo) -- necesaria en los 2 sentidos: cuando `#s-equipo` gana
   `.eq-panel-abierto` (fade out) Y cuando lo pierde (fade in, la regla de
   abajo deja de aplicar y el header vuelve a su opacity normal) -- sin la
   `transition` acá, ese 2do caso sería un salto seco, no una animación. */
.eq-grupo-header--stuck { position: fixed; z-index: 5; background: var(--bg); transition: opacity 0.2s ease; }
/* Fade out/in de los headers stuck mientras cualquier panel de la nav está
   abierto (bug real corregido, ver MANIFEST.md/CHANGELOG.md -- "acordeones
   se rompen cuando el panel está abierto") -- ver el comentario grande en
   `_eqSincronizarClasePanelAbierto()`/js/equipo.js para el porqué completo:
   en resumen, el `top`/`left`/`width` de estos headers solo se resincroniza
   300ms DESPUÉS de que termina la transición de `max-height` del panel
   (0.28s), así que durante esos ~280ms quedan mal posicionados/superpuestos
   mientras el panel se expande o se contrae -- en vez de perseguir esa
   sincronización frame a frame, se ocultan del todo mientras dura. Scopeado
   a `#s-equipo.eq-panel-abierto` (clase en la pantalla, no en cada header
   suelto) -- así un header que recién pasa a stuck MIENTRAS el panel sigue
   abierto (el usuario scrollea más sin cerrarlo) también nace oculto, sin
   depender de haber estado stuck en el instante exacto de abrir/cerrar.
   Los headers en posición NATURAL (sin `.eq-grupo-header--stuck`) no están
   alcanzados por este selector -- no se ven afectados (pedido explícito). */
#s-equipo.eq-panel-abierto .eq-grupo-header--stuck { opacity: 0; pointer-events: none; }
.eq-grupo-linea { flex: 1; height: 1px; background: var(--border-light); }
.eq-grupo-pill {
  flex-shrink: 0; background: var(--brand-light); color: var(--brand);
  font-size: 0.68rem; font-weight: 800; padding: 3px 9px; border-radius: 999px;
}
.eq-grupo-nombre {
  flex-shrink: 0; font-size: 0.82rem; font-weight: 800; color: var(--text);
  text-transform: uppercase; letter-spacing: 0.6px;
}
.eq-grupo-chevron { flex-shrink: 0; font-size: 1.2rem; color: var(--muted); transition: transform 0.25s; }
.eq-grupo-header.abierto .eq-grupo-chevron { transform: rotate(180deg); color: var(--brand); }
.eq-grupo-body { max-height: 0; overflow: hidden; transition: max-height 0.4s cubic-bezier(0.16,1,0.3,1); }
/* `.eq-grupo-body.abierto { max-height: 2000px; }` eliminada (Batch 3, bug
   real "lista de Mirlxs truncada") -- ese techo fijo recortaba cualquier
   grupo con más miembros de los que entraban en 2000px de alto, sin scroll
   ni aviso. `_eqToggleGrupo()`/js/equipo.js ahora fija el `max-height`
   inline con el `scrollHeight` real del contenido al abrir -- un inline
   siempre gana sobre esta clase, así que dejarla no hacía nada salvo
   confundir a un lector futuro sobre de dónde sale el alto real. */
.eq-grupo-body-inner { padding: 4px 0 10px; }

/* ── Fila de miembro (lista + favoritos) ─────────── */
.eq-miembro-fila {
  display: flex; align-items: center; gap: 12px; padding: 9px 4px;
  border-radius: 12px; cursor: pointer; transition: background 0.15s;
  -webkit-tap-highlight-color: transparent;
}
@media (hover: hover) { .eq-miembro-fila:hover { background: var(--brand-lightest); } }
.eq-miembro-fila:active { background: var(--brand-light); }
.eq-miembro-info { flex: 1; min-width: 0; }
.eq-miembro-nombre {
  font-size: 0.9rem; font-weight: 800; color: var(--text);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.eq-miembro-numero { font-weight: 700; color: var(--muted); font-size: 0.8rem; }
.eq-miembro-username {
  font-size: 0.78rem; color: var(--muted); margin-top: 1px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* ── Stats inline de la card de lista (Batch 4, _eqStatsInlineHtml()) ──── */
.eq-miembro-stats { display: flex; align-items: center; gap: 8px; margin-top: 3px; flex-wrap: wrap; }
.eq-mini-stat {
  display: inline-flex; align-items: center; gap: 3px;
  font-size: 0.72rem; font-weight: 700; color: var(--muted);
  pointer-events: none;
}
.eq-mini-stat .material-symbols-rounded { font-size: 0.9rem; color: var(--brand); }
/* `.eq-mini-tier-pill` (pill Q/M) eliminada (pedido explícito, ver
   MANIFEST.md -- "quitar pills Q/M, mantener solo chevrones de tendencia"):
   `_eqStatsInlineHtml()`/js/equipo.js ya no la renderiza. El chevron de
   tendencia que reemplazó a esa pill (texto pelado, condicionado a
   `termometro_pct_anterior` -- un campo que nunca llegó a existir en
   `getEquipo()`) también se sacó, y el círculo con fondo sutil que lo
   reemplazó (vivía acá mismo, en esta fila de stats) se movió a la foto de
   perfil como badge -- ver `.eq-avatar-badge-wrap`/`.eq-tendencia-badge`
   más abajo (css/equipo.css, junto a "Perfil de detalle"). Nada de
   indicador de tendencia queda en esta fila de stats. */
.eq-fav-btn {
  flex-shrink: 0; display: flex; align-items: center; justify-content: center;
  width: 38px; height: 38px; border-radius: 50%; background: none; border: none;
  color: var(--muted); cursor: pointer; -webkit-tap-highlight-color: transparent;
}
@media (hover: hover) { .eq-fav-btn:hover { background: var(--brand-lightest); } }
.eq-fav-btn .material-symbols-outlined { font-size: 1.3rem; }
.eq-fav-btn.activo { color: var(--brand); }

/* Fade in/out breve al togglear favorito (`data-eq-fav`, `_eqToggleFavorito()`/
   js/equipo.js) -- aplica tanto sobre `.eq-fav-btn` (fila de lista/favoritos)
   como sobre `.app-nav-icon-btn` del nav de detalle, ambos comparten el
   mismo atributo `data-eq-fav`. */
@keyframes eqFavPulse { 0% { opacity: 1; } 50% { opacity: 0; } 100% { opacity: 1; } }
.eq-fav-pulse { animation: eqFavPulse 0.3s ease; }

/* Fade al agregar/sacar una fila puntual de #eq-favoritos-lista
   (`_eqAnimarCambioFavorito()`/js/equipo.js) -- reemplaza el re-render
   completo que causaba el salto abrupto al reordenar (bug real, ver
   MANIFEST.md). */
.eq-fila-fade { transition: opacity 0.25s ease; }

/* ── Perfil de detalle (#s-equipo-perfil) ────────── */
#eq-perfil-nav { position: sticky; top: 0; z-index: 30; }
/* Mismo padding horizontal (16px) que `.app-nav` (css/nav.css, "la barra de
   navegación unificada -- todas las navs de la app usan estas clases") --
   ya coincidía en el valor, pero le faltaban `width`/`box-sizing`/
   `min-height`, los mismos que hacen que `.app-nav` nunca se vea más baja/
   angosta que el resto de las navs de la app (sin `box-sizing:border-box`,
   el padding se suma al 100% heredado del padre en vez de restarse, y sin
   `min-height` el alto queda 100% a merced del contenido) -- agregados acá
   para que esta nav quede pixel-consistente con el resto, no solo con el
   mismo número de padding declarado. */
.eq-perfil-nav-row {
  position: relative;
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 22px 0px; background: var(--bg); flex-shrink: 0;
  width: 100%; box-sizing: border-box; min-height: 56px;
}
/* Nombre en la nav al scrollear (`_eqNavTituloObservar()`, js/equipo.js):
   mismo `.app-nav-title` que el resto de las navs (absoluto, no empuja los
   botones), alineado a la izquierda con su `padding-left` heredado de
   `.app-nav-title` (css/nav.css). Solo el `padding-right` es propio: reserva
   los botones de acción -- `--eq-nav-lado` (inline, `_eqNavHtml()`) = 1 o 2
   botones; el gap entre ellos es el de `.app-nav-actions`. Esta fila no
   tiene el padding horizontal de `.app-nav` (lo pone `.contenedor`), así
   que `left` corre la caja ese mismo inset para que el texto arranque a la
   misma distancia de la flecha que en las demás navs. */
.eq-perfil-nav-titulo {
  left: calc(-1 * var(--space-screen));
  padding-right: calc(var(--nav-btn-size) * var(--eq-nav-lado, 1) + 10px * (var(--eq-nav-lado, 1) - 1) + 12px);
  opacity: 0;
  transition: opacity 0.22s ease;
}
.eq-perfil-nav-titulo.visible { opacity: 1; }
/* Botón WhatsApp -- verde de marca, no el naranja de énfasis del resto de
   .app-nav-icon-btn (ver "Cambios recientes" en colors.css: --wa-brand no
   deriva del color de énfasis, es un color de marca fijo). */
.eq-wa-btn { background: var(--wa-bg); color: var(--wa-brand); }
@media (hover: hover) { .eq-wa-btn:hover { background: var(--wa-bg-hover); } }
/* Botón de favorito de la nav de detalle (Batch 3, bug real "no muestra
   estado activo") -- `.app-nav-icon-btn` no trae ningún color de estado
   propio, a diferencia de `.eq-fav-btn.activo` (lista) -- mismo color,
   sin tocar tamaño/forma (esos ya los aporta `.app-nav-icon-btn`).
   Pedido posterior de Victor: el solo cambio de color se veía poco
   permanente en esta vista (a diferencia de la fila de lista, este botón
   vive solo en su propia nav, sin otras filas activas alrededor con las
   que contrastar) -- se suma relleno de fondo, mismo tratamiento que un
   ícono "activo" del resto de la nav. */
.eq-nav-fav-btn {
  transition: background 0.2s, color 0.2s;
}
.eq-nav-fav-btn.activo {
  background: var(--brand);
}
.eq-nav-fav-btn.activo .material-symbols-outlined {
  color: var(--white);
}

.eq-perfil-header { display: flex; flex-direction: column; align-items: center; text-align: center; padding: 4px 16px 22px; }
.eq-avatar-wrap { position: relative; width: 96px; height: 96px; }
.eq-avatar-grande { width: 96px; height: 96px; }
.eq-avatar-grande .avatar-pill-letter { font-size: 34px; }
/* `.eq-rol-pill` (pill de categoría superpuesta bajo la foto) eliminada
   (pedido explícito, re-ajuste, ver MANIFEST.md) -- la categoría
   Quindes/Mirlxs se movió a `.eq-perfil-pills-row--top` (más abajo, junto
   a pronombres), ya no vive sobre/bajo la foto. */
/* Badge de tendencia de termómetro sobre la foto (rediseño, ver
   MANIFEST.md -- vivía como círculo suelto en la fila de stats de la lista,
   ver el comentario de `.eq-mini-stat` más arriba; pedido explícito de
   moverlo encima de la foto, esquina inferior derecha, tipo badge). Mismo
   `p.tendencia`/getEquipo() de siempre ('sube'/'baja'/`null`, ver
   `_eqTendenciaBadgeHtml()`/js/equipo.js). Usado en 3 lugares:
   1) `.eq-avatar-wrap` de acá arriba (perfil de detalle, YA
      `position:relative` -- el badge entra directo, sin wrapper propio, ver
      `_eqPerfilContenidoHtml()`). 2) `.eq-avatar-badge-wrap` de abajo (fila
      de lista/favoritos y "Mis estadísticas" -- esas 2 fotos NO tenían
      ningún wrapper `position:relative` propio antes de esto, se agrega acá
      -- ver `_eqAvatarConTendenciaHtml()`/js/equipo.js). `right`/`bottom`
      negativos a propósito, semi-superpuesto al borde del círculo, no
      clavado adentro. `pointer-events:none` -- mismo criterio que el resto
      de indicadores sueltos de esta sección (`.eq-mini-stat`, arriba): el
      click siempre tiene que propagar al contenedor real
      (`_eqAbrirPerfil()`/toggle de favorito), nunca quedar atrapado acá.
      `z-index:2` (bug real corregido en el perfil de detalle cuando
      `.eq-rol-pill` todavía vivía en este wrapper -- pintaba encima del
      badge por orden de aparición; se deja el z-index igual, ya no hace
      falta pero tampoco molesta a nada).
   Fondo OPACO (pedido explícito, re-ajuste -- antes `--success-bg`/
   `--danger-bg`, que son rgba con alpha reducido en colors.css, pensadas
   para "fondo sutil" de chips sobre superficie plana; sobre una FOTO se
   veían demasiado tenues/casi invisibles). `--success`/`--danger` (sólidos,
   sin alpha, colors.css) + ícono en `--white` -- el único par de esta
   sección que es 100% opaco en los 2 temas (los `-bg`/`-lightest` de verde
   quedan con alpha en dark mode, confirmado leyendo colors.css antes de
   elegir este par). Tamaño base (18px/13px) para "Mis estadísticas"
   (`avatar-pill--md`, no pedido explícito de agrandar ahí) -- 2
   modificadores de tamaño para los 2 casos que sí pidieron agrandarse:
   `--card` (fila de lista, foto chica) y `--detalle` (perfil, foto grande,
   "se veía muy pequeño"). */
.eq-avatar-badge-wrap { position: relative; display: inline-flex; flex-shrink: 0; }
.eq-tendencia-badge {
  position: absolute; right: -2px; bottom: -2px; z-index: 2;
  display: flex; align-items: center; justify-content: center;
  width: 18px; height: 18px; border-radius: 50%; flex-shrink: 0;
  pointer-events: none;
}
.eq-tendencia-badge .material-symbols-outlined { font-size: 13px; }
.eq-tendencia-badge--card { width: 22px; height: 22px; }
.eq-tendencia-badge--card .material-symbols-outlined { font-size: 16px; }
.eq-tendencia-badge--detalle { width: 28px; height: 28px; }
.eq-tendencia-badge--detalle .material-symbols-outlined { font-size: 20px; }
.eq-tendencia-badge-sube { background: var(--success); color: var(--white); }
.eq-tendencia-badge-baja { background: var(--danger); color: var(--white); }
/* Badges de estado sobre el avatar ("De viaje"/"Lesionadx",
   `_eqBadgeAvatarHtml()`/js/equipo.js) -- misma esquina que
   `.eq-tendencia-badge` (lo reemplazan, no se apilan); el contenedor
   `.eq-avatar-badge-wrap` ya es `position:relative`. `pointer-events:none`/
   `z-index` igual que el de tendencia, para que el toque siga abriendo el
   perfil. `.badge-estado` tiene la geometría común; cada estado solo pone
   sus colores (tokens `--badge-*` en css/colors.css). */
.badge-estado {
  position: absolute;
  bottom: 0;
  right: 0;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  border: 1.5px solid var(--bg);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 2;
  pointer-events: none;
}
.badge-estado .material-symbols-rounded { font-size: 12px; }
/* Perfil de detalle (foto de 96px) -- mismo tamaño que
   `.eq-tendencia-badge--detalle`, el badge base se perdía ahí. */
.badge-estado--detalle { width: 28px; height: 28px; border-width: 2px; }
.badge-estado--detalle .material-symbols-rounded { font-size: 17px; }
/* Foto editable del detalle propio: tocar la foto o el badge de lápiz abre
   `abrirSheetFotoPerfil('equipo')` (js/foto.js). El badge va en la esquina
   SUPERIOR derecha (la inferior es del badge de estado/tendencia), mismo
   tamaño que `.badge-estado--detalle`; sin eventos propios, el toque lo
   recibe el wrap. */
.eq-avatar-editable { cursor: pointer; -webkit-tap-highlight-color: transparent; border-radius: 50%; }
.eq-avatar-editable .eq-avatar-grande { transition: opacity 0.15s; }
.eq-avatar-editable:active .eq-avatar-grande { opacity: 0.8; }
.eq-avatar-editable:focus-visible { outline: 2px solid var(--brand); outline-offset: 3px; }
.badge-editar-foto {
  position: absolute; top: 0; right: 0; z-index: 2;
  width: 28px; height: 28px; border-radius: 50%;
  border: 2px solid var(--bg); background: var(--brand); color: var(--white);
  display: flex; align-items: center; justify-content: center;
  pointer-events: none;
}
.badge-editar-foto .material-symbols-rounded { font-size: 15px; }

.badge-viaje { background: var(--badge-viaje-bg); }
.badge-viaje .material-symbols-rounded { color: var(--badge-viaje-icon); }
.badge-lesion { background: var(--badge-lesion-bg); }
.badge-lesion .material-symbols-rounded { color: var(--badge-lesion-icon); }
.eq-perfil-nombre { font-size: 1.3rem; font-weight: 800; color: var(--text); margin-top: 20px; }
.eq-perfil-sub { font-size: 0.85rem; color: var(--muted); margin-top: 3px; }

/* Fila de pills deslizable -- roles (ver comentario en
   _eqPerfilContenidoHtml()/js/equipo.js: sin dato real hoy, nunca se
   pinta en la práctica), sin scrollbar visible. */
.eq-perfil-pills-row {
  display: flex; gap: 8px; overflow-x: auto; padding: 18px 16px 2px; margin: 0 -16px;
  scrollbar-width: none;
}
.eq-perfil-pills-row::-webkit-scrollbar { display: none; }
.eq-perfil-pills-row .aj-pill { flex-shrink: 0; cursor: default; }
/* Fila "de arriba" -- categoría (Quindes/Mirlxs) + pronombres, justo debajo
   del nombre (pedido explícito, re-ajuste, ver MANIFEST.md) -- variante de
   `.eq-perfil-pills-row` de arriba, pero centrada y sin scroll/sangrado de
   borde (esta fila nunca tiene más de 2 pills, a diferencia de `roles`, que
   sí puede desbordar). */
/* `align-items: center` (bug real corregido, ver MANIFEST.md -- "pill
   Quindes/Mirlxs no está bien centrado"): esta fila es `display:flex` sin
   `align-items` propio (hereda el default `stretch`) -- `.eq-mis-stats-rol-pill`
   (padding 3px) y `.aj-pill` (padding 7px, pronombres) tienen alturas
   naturales distintas, así que la pill más chica se estiraba para igualar
   a la más alta cuando conviven las 2 en la fila; `display:inline-block`
   (no flex) no centra su propio texto dentro de esa caja estirada, el
   texto quedaba pegado arriba. `align-items:center` centra cada pill en su
   propia altura natural en vez de estirarla. */
.eq-perfil-pills-row--top {
  overflow: visible; justify-content: center; align-items: center; padding: 10px 0 0; margin: 0;
}
/* Pill de nivel (Quindes/Mirlxs) al mismo tamaño que la de pronombres
   (pedido explícito, re-ajuste, ver MANIFEST.md -- "mismo tamaño, padding
   y font-size que el pill de pronombres") -- pisa acá, SOLO en esta fila,
   el padding/font-size/radius chicos de `.eq-mis-stats-rol-pill` (arriba,
   2px 10px / 0.72rem) con los
   mismos valores EXACTOS de `.aj-pill` (css/perfil.css, pill de
   pronombres) -- color/borde/fondo de `.eq-mis-stats-rol-pill` quedan
   igual, solo cambia el tamaño de la caja. */
.eq-perfil-pills-row--top .eq-mis-stats-rol-pill {
  padding: 7px 16px; border-radius: 20px; font-size: 0.78rem;
}

/* ── Subsección "Estadísticas" del perfil de detalle (`#eq-perfil-sub`,
   `_eqSubPerfilHtml(p,'stats')`/js/equipo.js; antes un acordeón) -- achique
   compacto de `.eq-stat-card`/`.eq-rank-wrap`, selector de descendiente
   para no achicar ninguna otra card de la app. */
.eq-perfil-stats .eq-stats-grid { gap: 8px; margin: 10px 0 4px; }
.eq-perfil-stats .eq-stat-card { padding: 10px 8px; }
.eq-perfil-stats .eq-stat-icon { font-size: 1.15rem; margin-bottom: 4px; }
.eq-perfil-stats .eq-stat-valor { font-size: 15px; }
.eq-perfil-stats .eq-stat-label { font-size: 13px; margin-top: 1px; }
.eq-perfil-stats .eq-rank-wrap { margin: 10px 0 4px; }
/* Nota de período (re-ajuste, pedido explícito, ver MANIFEST.md -- "sin
   filtros de período en estadísticas del detalle") -- reemplaza las 2
   pills Fecha/Histórico que vivían acá (`.eq-perfil-stats-periodo`/
   `.eq-periodo-pills`, ver CHANGELOG.md) por un texto chico aclarando que
   el período se controla desde el filtro global de Equipo, sin UI propia
   en el detalle (mismo criterio que "Mis estadísticas", que tampoco tiene
   selector propio). `.eq-periodo-pills` sigue viva sin cambios: la usa el
   panel de Filtros de la lista. */
.eq-perfil-stats-nota { margin: 2px 0 12px; font-size: 0.72rem; color: var(--muted); }

/* Stats anuales -- 2 tarjetas lado a lado, fondo suave sin borde duro. */
.eq-stats-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin: 16px 0 6px; }
.eq-stat-card { background: var(--surface-2); border-radius: 16px; padding: 18px 12px; text-align: center; }
.eq-stat-card--full { grid-column: 1 / -1; }
.eq-stat-icon { display: block; font-size: 1.5rem; margin-bottom: 6px; color: var(--brand); }
.eq-stat-valor { font-size: 1.35rem; font-weight: 800; color: var(--text); }
.eq-stat-label { font-size: 0.72rem; color: var(--muted); font-weight: 600; margin-top: 2px; }

/* Barra de rango -- estilo "ranked meter": mismo `termometro_pct` (0-100,
   real desde el Cambio 59, ver getEquipo()/supabase/functions/api/index.ts)
   para Mirlxs (progreso hacia Quindes) y Quindes (margen antes de bajar a
   Mirlxs), solo cambia el texto contextual de abajo (`_eqRankTexto()`) -- el
   fill nunca se invierte. */
/* `max-height`/`overflow`/`transition` (Cambio 52) -- soportan el
   fade+colapso de `.eq-rank-oculto` cuando un admin fija la categoría a
   mano (`_eqCambiarTier()`, js/equipo.js) en vez de dejarla en "Auto".
   200px de techo (generoso, mismo criterio que el resto de los acordeones
   de max-height fijo de la app, ej. `.eq-grupo-body.abierto`) -- el
   contenido real (labels+track+texto) nunca se acerca a esa altura. */
.eq-rank-wrap { margin: 18px 0 6px; max-height: 200px; overflow: hidden; transition: opacity 0.3s ease, max-height 0.35s ease; }
.eq-rank-wrap.eq-rank-oculto { opacity: 0; max-height: 0; }
/* Solo para el frame inicial del perfil (`_eqRenderPerfil()`, js/equipo.js)
   -- si la categoría ya viene fijada a mano, el termómetro debe arrancar
   YA oculto, sin animar ese primer colapso. */
.eq-rank-wrap.sin-transicion { transition: none; }
.eq-rank-labels {
  display: flex; justify-content: space-between; margin-bottom: 6px;
  font-size: 0.68rem; font-weight: 700; color: var(--muted);
  text-transform: uppercase; letter-spacing: 0.4px;
}
.eq-rank-track { width: 100%; height: 8px; border-radius: 999px; background: var(--border-light); overflow: hidden; }
/* `width` inline (0% al render, subido a `termometro_pct`% en el siguiente
   frame por _eqRenderPerfil(), js/equipo.js) -- la transición de acá abajo
   es lo que la anima en vez de aparecer ya llena. */
.eq-rank-fill { height: 100%; width: 0%; border-radius: 999px; background: var(--brand); transition: width 0.6s ease; }
.eq-rank-texto { margin-top: 8px; font-size: 0.78rem; color: var(--muted); text-align: center; }

/* Filas de información de contacto -- separador sutil entre filas. */
.eq-info-lista { margin-top: 10px; }
.eq-info-fila {
  display: flex; align-items: center; gap: 14px; padding: 14px 4px;
  border-bottom: 1px solid var(--border-light); color: var(--text); text-decoration: none;
}
.eq-info-fila:last-child { border-bottom: none; }
.eq-info-fila .material-symbols-outlined { flex-shrink: 0; font-size: 1.2rem; color: var(--brand); }
.eq-info-texto { font-size: 0.88rem; font-weight: 600; }
/* Rol no definido (Batch 4) -- "color secundario y sin peso" pedido para
   el fallback de la fila de rol, css/equipo.css. */
.eq-info-texto-vacio { color: var(--muted); font-weight: 400; }
a.eq-info-fila:active { background: var(--brand-lightest); }

/* Control de categoría (tier), admin-only -- segmented control
   [Quindes | Auto | Mirlxs] del bottom sheet de Categoría del bloque
   Administración (`#eq-sheet-categoria`, `_eqCategoriaSheetHtml()`/
   `_eqCategoriaElegir()`, js/equipo.js). `--muted` para textos apagados (no
   existe `--text-muted` en css/colors.css). */
/* Pills de opción del bloque Administración (Categoría y Estado) -- clase
   base ÚNICA (pedido explícito 2026-09-28: mismas medidas en todas partes):
   mismo alto, padding, font-size y radio. `.eq-tier-btn`/`.eq-estado-btn`
   quedan solo como ganchos de JS (+ `flex:1` para las 3 de Categoría, que
   reparten el ancho; las 5 de Estado envuelven con `flex-wrap`). */
.eq-opcion-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 4px;
  min-height: 36px; padding: 8px 12px; border-radius: var(--radius);
  border: 1px solid var(--border); background: var(--surface); color: var(--muted);
  font-size: 0.8rem; font-weight: 700; line-height: 1.2; white-space: nowrap;
  cursor: pointer; font-family: inherit; transition: background 0.2s, color 0.2s, border-color 0.2s;
  -webkit-tap-highlight-color: transparent;
}
.eq-opcion-btn.activo { background: var(--brand); color: var(--white); border-color: var(--brand); }
.eq-opcion-btn .material-symbols-rounded { font-size: 1rem; }
.eq-tier-control { display: flex; gap: 6px; margin-bottom: 8px; }
.eq-tier-btn { flex: 1; }
.eq-tier-desc { font-size: 0.75rem; color: var(--muted); line-height: 1.5; }
/* Mientras guarda: sin más toques. Al confirmar el backend, check en la
   pill elegida un instante antes de que el sheet se cierre solo. */
.eq-tier-control.guardando { pointer-events: none; }
.eq-tier-btn-check { display: none; font-size: 1rem; }
.eq-tier-btn.confirmado .eq-tier-btn-check { display: inline-block; animation: eqCheckPop 0.25s var(--ease-sheet, ease) both; }
@keyframes eqCheckPop { from { transform: scale(0.4); opacity: 0; } to { transform: scale(1); opacity: 1; } }
#eq-sheet-categoria-body .eq-tier-desc { margin: 4px 0 8px; }

/* Subsección "Asistencias externas" (`_eqSubPerfilHtml(p, 'externas')`):
   botón de registrar arriba, lista `.aj-row` (fecha + lugar) con borrar a
   la derecha, y estado vacío/cargando. */
.eq-ext-registrar-btn { display: flex; align-items: center; justify-content: center; gap: 6px; margin-bottom: 16px; }
.eq-ext-registrar-btn .material-symbols-outlined { font-size: 1.2rem; }
.eq-ext-borrar {
  flex-shrink: 0; width: 36px; height: 36px; border: none; border-radius: 50%;
  background: transparent; color: var(--muted); display: flex; align-items: center; justify-content: center;
  cursor: pointer; transition: background 0.2s, color 0.2s; -webkit-tap-highlight-color: transparent;
}
.eq-ext-borrar .material-symbols-outlined { font-size: 1.25rem; }
.eq-ext-borrar:active { background: var(--danger-bg); color: var(--danger); }
@media (hover: hover) { .eq-ext-borrar:hover { background: var(--danger-bg); color: var(--danger); } }
.eq-ext-vacio {
  display: flex; flex-direction: column; align-items: center; gap: 8px;
  padding: 32px 16px; text-align: center; color: var(--muted); font-size: 0.85rem; line-height: 1.5;
}
.eq-ext-vacio p { margin: 0; }
.eq-ext-vacio .material-symbols-outlined { font-size: 2rem; }

.eq-admin-hint { font-size: 0.72rem; color: var(--muted); line-height: 1.45; margin-top: 8px; }

/* ── Grupos del perfil de detalle (100% estilo Ajustes, 2026-09-28) --
   grupo público (Estadísticas/Estado/lesión) y grupo "Administración"
   (solo admin). Reusan `.aj-group`/`.aj-row`/`.aj-icon`/`.aj-content`/
   `.aj-title`/`.aj-value`/`.aj-chevron` (css/perfil.css) y el switch
   `.toggle-btn` (css/global.css); acá solo lo propio del detalle. Mismo
   espacio entre grupos que Ajustes: solo el `margin-bottom:10px` de
   `.aj-group`, sin título de grupo. */
#eq-perfil-grupo-publico { margin-top: 16px; }
/* "Mis datos" (solo detalle propio, `_eqMisDatosHtml()`): primer grupo tras
   el header, mismo espacio arriba; el grupo público que le sigue queda
   separado solo por el `margin-bottom` de `.aj-group`, como en Ajustes. */
#eq-perfil-grupo-datos { margin-top: 16px; }
#eq-perfil-grupo-datos + #eq-perfil-grupo-publico { margin-top: 0; }
/* Los hints de las filas (cuota/admin, lesión) pueden ocupar 2 líneas -- a
   diferencia de Ajustes, donde `.aj-value` es un resumen de 1 línea. */
.eq-perfil-grupo .aj-value { white-space: normal; }
/* Fila de solo lectura (Estado para quien no es admin): sin cursor ni
   efecto de tap/hover, sin chevron (lo omite el JS). */
.aj-row.eq-fila-lectura { cursor: default; }
.aj-row.eq-fila-lectura:hover, .aj-row.eq-fila-lectura:active { background: transparent; }
.aj-row .aj-icon .material-symbols-rounded { font-size: 1.375rem; }
/* Fila de viaje dentro de la subsección Estado. */
.eq-admin-sub-viaje { margin-top: 16px; }
/* Fondo que retrocede mientras `#eq-admin-sub`/`#eq-desglose-panel` entran
   por encima -- misma transición que `#s-datos-card` (css/perfil.css). */
#eq-perfil-card { transition: transform 0.32s var(--ease-axis), opacity 0.32s var(--ease-axis); }

/* Botones de estado -- base `.eq-opcion-btn` (arriba); acá solo el
   contenedor que envuelve las 5 pills. */
.eq-estado-opciones { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
/* "Cancelar viaje" (admin) -- empujado a la derecha de la fila "De viaje"
   del perfil (`.eq-info-fila` es flex). */
.eq-viaje-cancelar { margin-left: auto; white-space: nowrap; flex-shrink: 0; }
/* Fila "De viaje" del perfil -- a la izquierda ícono + bloque de 2 líneas
   ("De viaje" / rango de fechas en gris, este último solo si hay fechas), a
   la derecha "Cancelar viaje" (admin). */
.eq-viaje-fila { display: flex; align-items: center; justify-content: space-between; }
/* `.btn-text-simple` (css/ui.css) trae `width:100%` + `padding-bottom:20px`
   pensado para botones de texto sueltos -- dentro de esta fila ocupaba todo
   el ancho sobrante y centraba "Cancelar viaje" en vez de pegarlo a la
   derecha. Ancho natural y sin ese padding acá. */
.eq-viaje-fila .eq-viaje-cancelar { width: auto; padding: 0; text-align: right; }
.eq-viaje-info { display: flex; align-items: center; gap: 10px; min-width: 0; }
.eq-viaje-titulo { font-weight: 600; }
.eq-viaje-fechas { font-size: 0.85em; font-weight: 400; color: var(--muted); white-space: nowrap; }

/* Bottom sheet de confirmación "dar acceso admin" (#eq-sheet-confirm-admin,
   index.html) -- hijo directo de <body>, mismo criterio de fade+slide que
   `.bsheet`/`.bsheet-overlay` (css/global.css), pero con clases propias
   (pedido explícito) en vez de reusar esas -- `--overlay-light`
   (`rgba(0,0,0,0.55)`) en vez del `rgba(0,0,0,0.5)` literal del pedido: es
   el token de oscurecimiento más cercano que ya existe en css/colors.css,
   más suave que `--overlay-dark` (0.7, el que usa `.bsheet-overlay`) a
   propósito -- esto es una confirmación liviana, no un flujo completo. */
.eq-confirm-sheet-overlay {
  position: fixed; inset: 0; background: var(--overlay-light);
  z-index: 9980; display: flex; align-items: flex-end;
  opacity: 0; pointer-events: none;
  transition: opacity 0.25s;
}
.eq-confirm-sheet-overlay.visible { opacity: 1; pointer-events: all; }
/* Bug real corregido -- "sheet transparente": `--surface` es un overlay
   TRANSLÚCIDO en oscuro (`rgba(255,255,255,0.06)`, pensado para una card
   apoyada sobre `--bg` ya pintado detrás, ver "CSS -- principios" en
   MANIFEST.md) -- este sheet no tiene ningún fondo opaco detrás (flota
   directo sobre el overlay semitransparente de arriba), así que en oscuro
   se veía prácticamente transparente. `--bg` (sólido, el mismo que usa
   `.bsheet`, css/global.css) + `border` completo (no solo `border-top`,
   mismo criterio que `.bsheet`) en vez de `--surface`. */
.eq-confirm-sheet {
  width: 100%; background: var(--bg);
  border-radius: 20px 20px 0 0; padding: 12px 20px 32px;
  border: 1px solid var(--border-light);
  transform: translateY(100%);
  transition: transform 0.3s cubic-bezier(.16,1,.3,1);
}
.eq-confirm-sheet-overlay.visible .eq-confirm-sheet { transform: translateY(0); }
.eq-confirm-sheet-handle { width: 36px; height: 4px; background: var(--border-light); border-radius: 2px; margin: 0 auto 16px; }
.eq-confirm-sheet-title { font-size: 1.05rem; font-weight: 800; color: var(--text); margin-bottom: 10px; }
.eq-confirm-sheet-msg { font-size: 0.88rem; color: var(--muted); line-height: 1.55; margin-bottom: 24px; }
.eq-confirm-sheet-btns { display: flex; gap: 10px; }
.eq-confirm-btn-cancel {
  flex: 1; padding: 12px; border-radius: var(--radius);
  border: 1px solid var(--border-light); background: var(--surface);
  color: var(--muted); font-size: 0.92rem; font-weight: 600; cursor: pointer; font-family: inherit;
}
.eq-confirm-btn-ok {
  flex: 1; padding: 12px; border-radius: var(--radius);
  border: none; background: var(--brand);
  color: var(--white); font-size: 0.92rem; font-weight: 700; cursor: pointer; font-family: inherit;
}

/* ── Desglose detallado de puntos por concepto (feat nueva, ver
   MANIFEST.md/CHANGELOG.md -- "subpantalla de desglose de puntos por
   concepto") -- `#eq-desglose-panel` (index.html) es el equivalente de
   `.aj-sub`/css/perfil.css (subsecciones de Ajustes, pedido explícito
   "igual a como se navega a subsecciones en Ajustes") en ESTA sección:
   mismos valores EXACTOS (`position:fixed` a pantalla completa salvo la
   nav inferior, `z-index:950`, `translateX(100%)`/`opacity:0.85` en
   reposo, mismos 0.32s `var(--ease-axis)`) -- clase propia en vez de
   reusar `.aj-sub` literal, mismo criterio de "cada sección es dueña de
   sus propias clases" ya establecido en el resto de este archivo (`.aj-sub`
   vive en css/perfil.css, sección ajena). `_eqAbrirDesglosePuntos()`/
   `_eqCerrarDesglosePuntos()` (js/equipo.js) animan `transform`/`opacity`
   a mano (mismo "shared axis X" que `irAjSub()`/`cerrarAjSub()`,
   js/perfil.js) -- esta clase solo aporta el estado de REPOSO (panel
   cerrado) y la `transition`, igual que `.aj-sub`. */
.eq-desglose-panel {
  display: none; position: fixed; top: 0; right: 0; left: 0;
  bottom: calc(var(--bottom-nav-h) + env(safe-area-inset-bottom, 0px));
  /* 960 > 950 de `.aj-sub`: el desglose se abre desde la subsección de
     Estadísticas (`#eq-perfil-sub`) y tiene que quedar encima. */
  background: var(--bg); z-index: 960; flex-direction: column; overflow: hidden;
  transform: translateX(100%); opacity: 0.85;
  transition: transform 0.32s var(--ease-axis), opacity 0.32s var(--ease-axis);
}
.eq-desglose-panel.activa { display: flex; }
.eq-desglose-panel-inner { overflow-y: auto; flex: 1; padding: var(--space-screen); }

/* Subtítulo de período (pedido explícito: "el período mostrado debe
   respetar el filtro activo") -- `_eqDesglosePeriodoTxt()`/js/equipo.js. */
.eq-desglose-periodo { font-size: 0.78rem; color: var(--muted); margin-bottom: 4px; }

/* Fila individual del desglose -- ícono redondo + título/subtítulo +
   valor a la derecha, mismo criterio general de fila que `.eq-info-fila`
   (perfil de detalle, más arriba) pero con un 3er bloque (`-valor`) que
   esa no tiene. */
.eq-desglose-fila {
  display: flex; align-items: center; gap: 12px; padding: 12px 4px;
  border-bottom: 1px solid var(--border-light);
}
.eq-desglose-fila:last-child { border-bottom: none; }
.eq-desglose-fila-icono {
  flex-shrink: 0; width: 40px; height: 40px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: var(--brand-light); color: var(--brand);
}
.eq-desglose-fila-icono .material-symbols-rounded { font-size: 1.2rem; }
.eq-desglose-fila-info { flex: 1; min-width: 0; }
.eq-desglose-fila-titulo { font-size: 0.88rem; font-weight: 700; color: var(--text); }
.eq-desglose-fila-sub { font-size: 0.78rem; color: var(--muted); margin-top: 1px; }
.eq-desglose-fila-valor { flex-shrink: 0; font-size: 0.9rem; font-weight: 800; color: var(--brand); }
/* Badge "Externa" (asistencia registrada por admin fuera del club, ver
   `adminRegistrarAsistenciaExterna`/MANIFEST.md) -- neutro a propósito, NO
   el verde de `.badge-confirmada` de una asistencia normal (no existe un
   token `--accent` en css/colors.css; se usan los grises de superficie). */
.eq-badge-externa {
  display: inline-block; vertical-align: middle; margin-left: 6px;
  padding: 1px 7px; border-radius: 10px;
  background: var(--surface-3); border: 1px solid var(--border-warm); color: var(--text-2);
  font-size: 0.66rem; font-weight: 700; letter-spacing: 0.3px; text-transform: uppercase;
}

/* Total, al final de la lista (pedido explícito) -- separador propio,
   mismo peso visual que `.eq-desglose-fila-valor` pero más grande, para
   distinguirlo de una fila más. */
.eq-desglose-total {
  display: flex; align-items: center; justify-content: space-between;
  padding: 16px 4px 4px; margin-top: 4px; border-top: 1.5px solid var(--border-mid);
  font-size: 0.92rem; font-weight: 800; color: var(--text);
}
.eq-desglose-total-valor { color: var(--brand); }

/* Cards de puntos tappables (Puntos por tareas/asistencia/racha, "Mis
   estadísticas" y perfil de detalle) -- affordance de tap mínima sobre
   `.eq-stat-card` ya existente (scoped como modificador, nunca la clase
   base a secas -- las cards de horas/asistencia anual NO son tappables,
   se quedan sin este modificador). */
.eq-stat-card--tappable { cursor: pointer; -webkit-tap-highlight-color: transparent; transition: background 0.15s; }
.eq-stat-card--tappable:active { background: var(--brand-light); }

/* ── Pill "Tú" en la fila propia del roster (ver MANIFEST.md -- la usuaria
   actual vive en su grupo, primera posición) -- chica, junto al nombre,
   mismo par brand-light/brand que el resto de pills de esta sección. */
.eq-tu-pill {
  display: inline-flex; align-items: center; vertical-align: middle;
  margin-left: 6px; padding: 1px 8px; border-radius: 999px;
  font-size: 0.66rem; font-weight: 800; line-height: 1.5;
  background: var(--brand-light); color: var(--brand);
}
