/*! =============================================================================
 *  © 2026 Renzo Núñez · RonBStudio — "Perú Elige 2026 · Segunda Vuelta".
 *  Todos los derechos reservados. Código propietario: queda prohibida su copia,
 *  redistribución, ingeniería inversa o uso no autorizado, total o parcial.
 *  https://www.ronbstudio.com
 * ============================================================================= */
/* =============================================================================
 *  Proyecto      : Perú Elige 2026 — Segunda Vuelta
 *  Archivo       : race-header.css
 *  Descripción   : Header tipo Bloomberg / AP election map.
 *
 *                  v0.1.14.7 cambios:
 *                    · Sistema "boost" (reemplaza is-pulsing-1/2/3):
 *                      animación de "latido" simultáneo en 3 elementos
 *                      del lado del candidato favorecido (avatar wrap,
 *                      flechitas delta, % grande). Número de latidos
 *                      según magnitud del Δ (2 / 3 / 5). Configurable
 *                      via CSS vars --race-boost-beat-ms y
 *                      --race-boost-count.
 *
 *                  v0.1.6.4 cambios:
 *                    · Fix: lado derecho ahora usa flex-start (que en
 *                      row-reverse equivale al borde derecho real)
 *                    · Aura/ki alrededor de cada avatar con el color del
 *                      partido, animación pulse infinito desfasado entre
 *                      los dos lados
 *                    · Votos absolutos AHORA dentro de cada segmento de
 *                      la barra (texto blanco con sombra para legibilidad)
 *                    · Footer eliminado (los votos viven dentro de la barra)
 *                    · Animación delta al refrescar: 1/2/3 triángulos
 *                      arriba del % según magnitud + latido escalado
 *
 *                  Convenciones BEM. Cero hex. Cero magic numbers fuera
 *                  de las constantes nombradas.
 *
 *  Autor         : Renzo Núñez · RonBStudio
 *  Versión       : 0.1.14.13
 *
 *  v0.1.14.13 cambios:
 *    · Quick fix: -15% sobre v0.1.14.12 (calibración final).
 *      Avatar:   1.07 → 1.06   + halo 8 → 7px
 *      %:        1.09 → 1.08   + glow 8+2 → 7+2px
 *      Flechitas: 1.20 → 1.17   + glow 8+2 → 7+2px
 *
 *  v0.1.14.12 cambios:
 *    · Quick fix: -30% sobre v0.1.14.11 (sensación aún más sutil).
 *      Avatar:   1.10 → 1.07   + halo 11 → 8px
 *      %:        1.13 → 1.09   + glow 11+3 → 8+2px
 *      Flechitas: 1.28 → 1.20   + glow 11+3 → 8+2px
 *
 *  v0.1.14.11 cambios:
 *    · Quick fix: -20% sobre v0.1.14.10 (sensación más sutil).
 *      Avatar:   1.13 → 1.10   + halo 14 → 11px
 *      %:        1.16 → 1.13   + glow 14+4 → 11+3px
 *      Flechitas: 1.35 → 1.28   + glow 14+4 → 11+3px
 *
 *  v0.1.14.10 cambios:
 *    · Calibración de escalas tras dos iteraciones (1.08 imperceptible y
 *      1.18 con overshoot brusco). Punto medio honesto con ease-in-out
 *      simétrico (sin pump/undershoot). Halos más gruesos como refuerzo
 *      visual sin aumentar la sensación de brusquedad.
 *      Avatar:   1.08 → 1.13   + halo 8px → 14px
 *      %:        1.13 → 1.16   + glow doble 14+4px
 *      Flechitas: 1.25 → 1.35  + glow doble 14+4px
 * ===========================================================================*/

/* ===========================================================================
 *  Contenedor
 * =========================================================================*/

.race-header {
  max-width: var(--container-max);
  margin: 0 auto var(--sp-3);
  padding: 0 var(--container-padding);
}

/* ===========================================================================
 *  Top row: candidatos + threshold
 * =========================================================================*/

.race-header__top {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: var(--sp-4);
  margin-bottom: var(--sp-2);
}

.race-header__side {
  display: flex;
  align-items: center;
  gap: var(--sp-4);
}

/* v0.1.6.4 fix: en flexbox con row-reverse, los ejes están invertidos.
   `flex-end` empuja al inicio del eje invertido (que visualmente es el
   centro). Para que el contenido vaya al EXTREMO derecho real usamos
   `flex-start` (que en row-reverse es el borde derecho). */
.race-header__side--left {
  justify-content: flex-start;     /* extremo izquierdo */
}
.race-header__side--right {
  flex-direction: row-reverse;
  justify-content: flex-start;     /* extremo derecho (eje invertido) */
}

/* ---------- Wrapper del avatar (v0.1.6.4 → v0.1.6.5) ----------
   El wrapper se mantiene por estructura pero ya no hospeda el aura.
   En v0.1.6.5 movimos el aura del pseudo-elemento (que emanaba hacia
   adentro) a un box-shadow del avatar (siempre exterior, mucho más sutil). */

.race-header__avatar-wrap {
  position: relative;
  display: inline-block;
  flex-shrink: 0;
  width:  var(--race-avatar-size, 96px);
  height: var(--race-avatar-size, 96px);
}

@media (prefers-reduced-motion: reduce) {
  .race-header__avatar {
    animation: none;
  }
}

/* ---------- Avatar (la foto circular) ---------- */

.race-header__avatar {
  width:  100%;
  height: 100%;
  border-radius: 50%;
  background-image: var(--race-photo-url);
  background-size: cover;
  background-position: center var(--race-photo-y, 25%);
  background-repeat: no-repeat;
  border: 4px solid var(--race-border, var(--c-mv-border));
  position: relative;
  /* v0.1.6.5: aura via box-shadow exterior. La animación pulsa el blur
     de un halo coloreado del partido — sutil, NO emanante hacia adentro.
     La sombra base oscura se reemplaza por la del aura para que solo
     haya un set animado de shadows. */
  box-shadow:
    0 4px 10px rgba(0, 0, 0, 0.12),
    0 0 6px var(--race-aura-color, transparent);
  animation: race-aura-glow var(--race-aura-duration, 3s) ease-in-out infinite;
  /* Hover sutil para sensación táctil. */
  transition: transform var(--duration-default) ease;
}
.race-header__avatar-wrap:hover .race-header__avatar {
  transform: scale(1.05);
}

/* Pulse del aura: solo cambia el blur del halo coloreado. La sombra
   inferior oscura se mantiene constante. */
@keyframes race-aura-glow {
  0%, 100% {
    box-shadow:
      0 4px 10px rgba(0, 0, 0, 0.12),
      0 0 4px var(--race-aura-color, transparent);
  }
  50% {
    box-shadow:
      0 4px 10px rgba(0, 0, 0, 0.12),
      0 0 14px var(--race-aura-color, transparent);
  }
}

/* ---------- Bloque info (delta + % + nombre + partido) ---------- */

.race-header__info {
  display: flex;
  flex-direction: column;
  gap: 2px;
  position: relative;
}
.race-header__side--left .race-header__info  { text-align: right; }
.race-header__side--right .race-header__info { text-align: left;  }

/* ---------- Línea de triángulos delta (v0.1.6.4) ----------
   Reservada SIEMPRE como espacio fijo arriba del %, para que cuando
   aparezcan los triángulos no haya layout shift. */

.race-header__delta {
  display: flex;
  gap: 5px;
  height: 14px;
  align-items: flex-end;
  margin-bottom: 4px;
}
.race-header__side--left  .race-header__delta { justify-content: flex-end;   }
.race-header__side--right .race-header__delta { justify-content: flex-start; }

.race-header__delta-arrow {
  width: 0;
  height: 0;
  border-left:   7px solid transparent;
  border-right:  7px solid transparent;
  /* El borde inferior es el "color" visual del triángulo. */
  border-bottom: 12px solid var(--race-color, var(--c-mv-ink));
  /* Glow del color del partido alrededor del triángulo. */
  filter: drop-shadow(0 0 6px var(--race-color, transparent));
  /* Estado inicial — invisible y desplazado para que la animación
     race-delta-rise tenga de dónde levantar. */
  opacity: 0;
  transform: translateY(20px) scale(0.5);
  animation: race-delta-rise 600ms cubic-bezier(0.34, 1.8, 0.64, 1) forwards;
}
.race-header__delta-arrow:nth-child(2) { animation-delay: 80ms; }
.race-header__delta-arrow:nth-child(3) { animation-delay: 160ms; }

@keyframes race-delta-rise {
  to {
    opacity: 1;
    transform: translateY(0) scale(1);
  }
}

/* v0.1.6.5: las flechitas se mantienen visibles hasta el próximo
   refresh (la lógica JS las reemplaza/borra entonces). Antes hacían
   fade out después de 2 segundos — eso se removió por pedido del usuario. */

/* ---------- % grande del candidato ---------- */

.race-header__pct {
  font-family: var(--font-mono);
  font-weight: var(--fw-semibold);
  font-size: var(--race-pct-size, 2.25rem);
  font-variant-numeric: tabular-nums;
  line-height: 1;
  letter-spacing: -0.03em;
  color: var(--race-color, var(--c-mv-ink));
  margin-bottom: var(--sp-1);
  transform-origin: center center;
}

/* ----- Animación slam al cargar (v0.1.6.3) ----- */

.race-header.is-slamming .race-header__pct {
  animation: race-slam 700ms cubic-bezier(0.22, 1, 0.36, 1) backwards;
}
.race-header.is-slamming .race-header__side--right .race-header__pct {
  animation-delay: 80ms;
}

@keyframes race-slam {
  0% {
    transform: scale(2.8);
    opacity: 0;
    filter: blur(12px);
    letter-spacing: 0.15em;
  }
  60% {
    transform: scale(1.08);
    opacity: 1;
    filter: blur(0);
    letter-spacing: -0.05em;
  }
  80% { transform: scale(0.97); }
  100% {
    transform: scale(1);
    letter-spacing: -0.03em;
  }
}

/* ============================================================================
 *  SISTEMA "BOOST" — latido del candidato favorecido tras un refresh
 *  ============================================================================
 *  Reemplaza el sistema previo de `.is-pulsing-1/2/3` que solo afectaba al %
 *  grande. Ahora el latido se aplica simultáneamente a 3 elementos del lado
 *  del candidato que recibió más votos:
 *
 *    1. .race-header__avatar-wrap  — todo el conjunto avatar + aura
 *    2. .race-header__delta-arrow  — los triángulos que indican intensidad
 *    3. .race-header__pct          — el porcentaje grande
 *
 *  La activación es por clase `.is-boosting` aplicada al `.race-header__side`
 *  (un solo classList.add en JS, los 3 hijos heredan vía descendant selectors
 *  — mantiene sincronía garantizada entre los 3 elementos).
 *
 *  Variables CSS de configuración (con defaults seguros):
 *    --race-boost-beat-ms   duración de UN latido (default 550ms)
 *    --race-boost-count     cantidad de latidos (default 1)
 *
 *  El JS setea `--race-boost-count` vía style.setProperty() según la
 *  magnitud del cambio (2 / 3 / 5 latidos para menor / medio / grande).
 *
 *  Las flechitas tienen un caso especial: arrancan con `race-delta-rise`
 *  (su entrada animada existente) y luego se incorporan al latido con un
 *  delay de 800ms (cubre los 600ms del rise + 160ms del último stagger
 *  + un pequeño margen). El avatar wrap y el % laten desde t=600ms para
 *  quedar sincronizados con el primer latido de las flechitas.
 *
 *  prefers-reduced-motion: las animaciones de boost se desactivan al
 *  final del archivo en el bloque @media de accesibilidad.
 * ==========================================================================*/

/* Duraciones y delays — valores centralizados para que ajustes futuros sean
   un solo punto de cambio.
   v0.1.14.8: onset-ms reducido a 0 para que avatar y % laten desde el frame 1
   junto con la aparición de las flechitas. Antes esperábamos 600ms a que el
   rise terminara, pero eso dejaba al avatar/% "muertos" durante el primer
   segundo — la sensación era "solo aparecen las flechitas". */
:root {
  /* Duración de UN latido. El boost total dura beat-ms × count. */
  --race-boost-beat-ms:  550ms;
  /* Avatar y % laten desde el inicio (sin delay). */
  --race-boost-onset-ms: 0ms;
  /* Flechitas siguen con onset para no romper su rise (600ms + 160ms stagger + margen). */
  --race-boost-arrow-onset-ms: 800ms;
}

/* ---- Easing del latido ----
   ease-in-out: curva simétrica, suave. Sin overshoot. Sensación de pulso
   suave tipo corazón en reposo, no de pump explosivo.
   (v0.1.14.8 usó cubic-bezier(0.34, 1.56, ...) con overshoot pero se sintió
   demasiado brusco; revertido a ease-in-out clásico). */

/* Avatar wrap: escala sutil + halo coloreado que pulsa al unísono.
   El box-shadow del aura base sigue corriendo independiente; este filter
   extra se acumula en el peak para reforzar visualmente. */
.race-header__side.is-boosting .race-header__avatar-wrap {
  animation:
    race-boost-avatar
    var(--race-boost-beat-ms)
    ease-in-out
    var(--race-boost-onset-ms)
    var(--race-boost-count, 1);
}

@keyframes race-boost-avatar {
  0%, 100% {
    transform: scale(1);
    filter: drop-shadow(0 0 0 transparent);
  }
  50% {
    /* v0.1.14.13: -15% sobre v0.1.14.12. 6% de escalado + halo 7px. */
    transform: scale(1.06);
    filter: drop-shadow(0 0 7px var(--race-aura-color, transparent));
  }
}

/* % grande: escala un poco más pronunciada que el avatar (el % es el ancla
   visual del lado) + glow del color del partido. */
.race-header__side.is-boosting .race-header__pct {
  animation:
    race-boost-pct
    var(--race-boost-beat-ms)
    ease-in-out
    var(--race-boost-onset-ms)
    var(--race-boost-count, 1);
}

@keyframes race-boost-pct {
  0%, 100% {
    transform: scale(1);
    text-shadow: none;
  }
  50% {
    /* v0.1.14.13: -15% sobre v0.1.14.12. 8% de escalado + glow 7+2px. */
    transform: scale(1.08);
    text-shadow: 0 0 7px var(--race-color, transparent),
                 0 0 2px var(--race-color, transparent);
  }
}

/* Flechitas: las más expresivas (son chicas, necesitan algo más de escala
   para que el latido se note). Glow moderado del color del partido. */
.race-header__side.is-boosting .race-header__delta-arrow {
  animation:
    race-delta-rise 600ms cubic-bezier(0.34, 1.8, 0.64, 1) forwards,
    race-boost-arrow
      var(--race-boost-beat-ms)
      ease-in-out
      var(--race-boost-arrow-onset-ms)
      var(--race-boost-count, 1);
}
.race-header__side.is-boosting .race-header__delta-arrow:nth-child(2) {
  animation-delay: 80ms, var(--race-boost-arrow-onset-ms);
}
.race-header__side.is-boosting .race-header__delta-arrow:nth-child(3) {
  animation-delay: 160ms, var(--race-boost-arrow-onset-ms);
}

@keyframes race-boost-arrow {
  0%, 100% {
    transform: scale(1);
    filter: drop-shadow(0 0 6px var(--race-color, transparent));
  }
  50% {
    /* v0.1.14.13: -15% sobre v0.1.14.12. 17% de escalado + glow 7+2px. */
    transform: scale(1.17);
    filter: drop-shadow(0 0 7px var(--race-color, transparent))
            drop-shadow(0 0 2px var(--race-color, transparent));
  }
}

@media (prefers-reduced-motion: reduce) {
  .race-header__side.is-boosting .race-header__avatar-wrap,
  .race-header__side.is-boosting .race-header__pct,
  .race-header__side.is-boosting .race-header__delta-arrow {
    animation: none;
  }
}

.race-header__name {
  font-family: var(--font-serif);
  font-size: var(--fs-md);
  font-weight: var(--fw-medium);
  color: var(--c-mv-ink);
  line-height: 1.1;
}

.race-header__party {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--c-mv-ink-soft);
}

/* =============================================================================
 *  Threshold central — Brecha viva (v0.1.9.9)
 *
 *  Reemplaza el viejo "50% / para ganar / ▼" por un bloque que comunica
 *  la BRECHA entre candidatos en tiempo real, con:
 *    · Punto rojo + label "LIVE" pulsantes — sensación de tiempo real
 *    · Etiqueta uppercase "BRECHA"
 *    · Número grande del valor absoluto de la diferencia, coloreado con
 *      el color del partido líder (si hay líder)
 *    · Label "VOTOS"
 *    · Animación breathe sutil en el número para reforzar "vivo"
 *
 *  El layout se conserva (mismo wrap, mismo lugar). Las clases viejas
 *  (.race-header__threshold-pct/label/arrow) se eliminan porque el JS
 *  ya no las inyecta — se mantiene el legacy solo si algún snapshot
 *  externo lo referencia.
 * ===========================================================================*/

.race-header__threshold {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  font-family: var(--font-mono);
  text-align: center;
  min-width: 130px;
}

/* Fila superior: indicador LIVE con punto pulsante. */
.race-header__brecha-pulse {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  margin-bottom: 4px;
}

.race-header__brecha-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--c-live-red);
  box-shadow: 0 0 0 0 var(--c-live-red-pulse-on);
  animation: race-header-brecha-pulse 1.6s ease-out infinite;
}

.race-header__brecha-live {
  font-family: var(--font-mono);
  font-size: 9px;
  font-weight: var(--fw-bold);
  letter-spacing: 0.15em;
  color: var(--c-live-red);
}

/* Etiqueta uppercase "BRECHA". */
.race-header__brecha-label {
  font-family: var(--font-mono);
  font-size: 9.5px;
  font-weight: var(--fw-bold);
  letter-spacing: 0.18em;
  color: var(--c-mv-ink-soft);
  text-transform: uppercase;
}

/* Número grande del valor de la brecha. El color se aplica via
   modificador en el wrap padre (.is-leading-a o .is-leading-b). */
.race-header__brecha-val {
  font-family: var(--font-serif);
  font-weight: var(--fw-bold);
  font-size: 26px;
  line-height: 1;
  font-variant-numeric: tabular-nums;
  margin-top: 1px;
  color: var(--c-mv-ink);
  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.60);
  animation: race-header-brecha-breathe 3s ease-in-out infinite;
  transition: color 300ms var(--easing-standard);
}

.race-header__threshold.is-leading-a .race-header__brecha-val {
  color: var(--c-party-fp);
}

.race-header__threshold.is-leading-b .race-header__brecha-val {
  color: var(--c-party-ap);
}

/* Unidad "VOTOS" debajo del número. */
.race-header__brecha-unit {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--c-mv-ink-muted);
  margin-top: 2px;
}

/* Keyframes del indicador LIVE (anillo pulsante rojo). */
@keyframes race-header-brecha-pulse {
  0%   { box-shadow: 0 0 0 0   var(--c-live-red-pulse-on); }
  70%  { box-shadow: 0 0 0 8px var(--c-live-red-pulse-off); }
  100% { box-shadow: 0 0 0 0   var(--c-live-red-pulse-off); }
}

/* Respiración sutil del número grande (refuerza sensación "vivo"). */
@keyframes race-header-brecha-breathe {
  0%, 100% { transform: scale(1);     opacity: 1;    }
  50%      { transform: scale(1.025); opacity: 0.92; }
}

/* ===========================================================================
 *  Barra horizontal unificada
 *  v0.1.6.4: votos absolutos AHORA viven dentro de cada segmento.
 * =========================================================================*/

.race-header__bar {
  position: relative;
  display: flex;
  /* Más altura para que los votos quepan adentro con holgura. */
  height: 44px;
  border-radius: var(--radius-pill);
  overflow: hidden;
  background: var(--c-mv-border-soft);
  box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.18);
}

.race-header__bar-seg {
  position: relative;
  height: 100%;
  transition: width var(--race-bar-duration, 800ms) cubic-bezier(0.16, 1, 0.3, 1);
  /* IMPORTANTE: el segmento ya no usa overflow hidden — antes lo tenía
     para contener el shimmer, pero como los votos viven en el bar (no en
     el seg), el overflow se mueve al .race-header__bar. */
}

.race-header__bar-seg--left  { background: var(--c-party-fp); }
.race-header__bar-seg--right { background: var(--c-party-ap); }

/* Incandescencia interna. */
.race-header__bar-seg::before {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(
    180deg,
    rgba(255, 255, 255, 0.40) 0%,
    rgba(255, 255, 255, 0.10) 35%,
    rgba(0,   0,   0,   0.00) 60%,
    rgba(0,   0,   0,   0.18) 100%
  );
  pointer-events: none;
}

/* Shimmer que pasa cada 4s. */
.race-header__bar-seg::after {
  content: '';
  position: absolute;
  top: 0;
  bottom: 0;
  width: 30%;
  background: linear-gradient(
    90deg,
    transparent 0%,
    rgba(255, 255, 255, 0.22) 50%,
    transparent 100%
  );
  transform: translateX(-100%);
  animation: race-bar-shimmer 4s ease-in-out infinite;
  pointer-events: none;
}
.race-header__bar-seg--right::after { animation-delay: 2s; }

@keyframes race-bar-shimmer {
  0%   { transform: translateX(-100%); }
  60%  { transform: translateX(400%);  }
  100% { transform: translateX(400%);  }
}

@media (prefers-reduced-motion: reduce) {
  .race-header__bar-seg::after { animation: none; }
}

/* Línea vertical del threshold 50%. */
.race-header__bar-threshold {
  position: absolute;
  top: -6px;
  bottom: -6px;
  left: 50%;
  width: 2px;
  background: var(--c-mv-ink);
  transform: translateX(-50%);
  z-index: 3;
}
.race-header__bar-threshold::before,
.race-header__bar-threshold::after {
  content: '';
  position: absolute;
  left: 50%;
  width: 8px;
  height: 8px;
  background: var(--c-mv-ink);
  transform: translateX(-50%) rotate(45deg);
}
.race-header__bar-threshold::before { top: -4px;  }
.race-header__bar-threshold::after  { bottom: -4px; }

/* ---------- Votos absolutos DENTRO de la barra (v0.1.6.4) ----------
   Posicionados absolute relative al .race-header__bar, anclados al borde
   exterior (izquierdo o derecho). Texto blanco con sombra suave para
   legibilidad sobre cualquier color del partido. */

.race-header__bar-votos {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.02em;
  color: var(--c-mv-ink-on-accent);
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.35);
  white-space: nowrap;
  z-index: 2;
  pointer-events: none;
}
.race-header__bar-votos--left  { left:  var(--sp-3); }
.race-header__bar-votos--right { right: var(--sp-3); }

/* ---------- Diferencia de votos bajo el centro de la barra ---------- */

.race-header__bar-diff {
  position: absolute;
  top: calc(100% + 10px);
  left: 50%;
  transform: translateX(-50%);
  white-space: nowrap;
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  font-weight: var(--fw-medium);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.04em;
  color: var(--c-mv-ink);
  padding: 3px var(--sp-2);
  background: var(--c-mv-bg-card);
  border: 1px solid var(--c-mv-border-soft);
  border-radius: var(--radius-pill);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
}

/* ===========================================================================
 *  Acciones (progreso ONPE + botón Refrescar)
 *  Más margin arriba para hacerle espacio al badge de Δ votos.
 * =========================================================================*/

.race-header__actions {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  justify-content: center;
  flex-wrap: wrap;
  margin-top: var(--sp-5);
}

/* v0.1.9.9 — Botón Refrescar premium:
   - Gradiente azul (blue-500 → blue-600) en lugar del slate plano.
   - Sombras multi-capa con tinte azul para sensación de elevación.
   - Inset highlight blanco translúcido arriba (efecto "tecla").
   - Hover sube el botón con sombra más fuerte; active lo hunde con inset. */
.race-header__refresh {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  padding: 9px;   /* v0.1.16.171: botón solo-ícono (el label va oculto) */
  background: linear-gradient(180deg, var(--c-btn-primary-top) 0%, var(--c-btn-primary-bot) 100%);
  color: var(--c-mv-ink-on-accent);
  border: 1px solid var(--c-btn-primary-inset);
  border-radius: var(--radius-pill);
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  box-shadow:
    0 0 0 1px var(--c-btn-primary-ring),
    0 1px 2px var(--c-btn-primary-shadow),
    0 2px 6px var(--c-btn-primary-shadow-soft),
    inset 0 1px 0 var(--c-btn-primary-inset);
  cursor: pointer;
  transition: all 180ms cubic-bezier(0.16, 1, 0.3, 1);
}
.race-header__refresh:hover {
  background: linear-gradient(180deg, var(--c-btn-primary-top-hover) 0%, var(--c-btn-primary-bot-hover) 100%);
  box-shadow:
    0 0 0 1px var(--c-btn-primary-ring-hover),
    0 2px 4px var(--c-btn-primary-shadow),
    0 4px 12px var(--c-btn-primary-shadow-soft),
    inset 0 1px 0 var(--c-btn-primary-inset-hover);
  transform: translateY(-1px);
}
.race-header__refresh:active {
  transform: translateY(0);
  box-shadow:
    0 0 0 1px var(--c-btn-primary-ring),
    0 1px 2px var(--c-btn-primary-shadow-soft),
    inset 0 2px 4px rgba(0, 0, 0, 0.10);
}
.race-header__refresh:disabled { opacity: 0.7; cursor: wait; }
/* v0.1.16.171: el texto "Refrescar resultados" se oculta — botón solo-ícono. */
.race-header__refresh-label { display: none; }

/* v0.1.16.172 — MÓVIL: el "Ganador matemático" y el botón refresh en UNA sola
   fila. El indicador ocupa el ancho disponible; el botón chico queda fijo al
   extremo derecho (antes envolvían a línea completa cada uno). */
@media (max-width: 768px) {
  .race-header__actions {
    flex-wrap: nowrap;
    justify-content: space-between;
    align-items: center;
    gap: 8px;
  }
  /* v0.1.16.175: el flex item REAL es el contenedor del indicador (un div sin
     clase hasta ahora), NO el .ganmat interno. Ese contenedor debe poder encoger
     para que el botón refresh nunca se salga de la pantalla en celulares angostos. */
  .race-header__gm-mount {
    flex: 1 1 auto;
    min-width: 0;
  }
  .race-header__gm-mount .ganmat { min-width: 0; max-width: none; }
  .ganmat__head { min-width: 0; }
  .ganmat__label {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    letter-spacing: 0.06em;   /* más compacto en móvil para que entre con el número */
  }
  .ganmat__faltan { flex: 0 0 auto; }
  /* v0.1.16.177: override del `.race-header__refresh{width:100%}` viejo del bloque
     ≤480px (diseño anterior, botón solo a ancho completo). Ahora el botón va junto
     al indicador → debe ser un ícono chico, no full-width (si no, empuja todo fuera). */
  .race-header__actions .race-header__refresh {
    flex: 0 0 auto;
    width: auto;
    min-width: 0;
    padding: 9px;
  }
}
.race-header__refresh.is-loading svg {
  animation: race-header__spin 0.8s linear infinite;
}
@keyframes race-header__spin {
  to { transform: rotate(360deg); }
}

/* ===========================================================================
 *  Accesibilidad: prefers-reduced-motion (cobertura completa)
 *  Los bloques @media específicos arriba ya cubren aura, pct-pulse y
 *  bar-shimmer. Acá completamos las 3 animaciones que quedaban afuera:
 *    · race-slam            (boot del componente)
 *    · race-delta-rise      (flechitas que aparecen al refresh)
 *    · race-header__spin    (spinner del botón Refrescar en loading)
 *  Si el usuario pide motion reducido, simplemente no se animan.
 * =========================================================================*/

@media (prefers-reduced-motion: reduce) {
  .race-header.is-slamming .race-header__pct,
  .race-header__delta-arrow,
  .race-header__refresh.is-loading svg,
  .race-header__brecha-dot,
  .race-header__brecha-val,
  .race-header__threshold.is-palpito {
    animation: none;
  }
}

/* ===========================================================================
 *  HITO HISTÓRICO (v0.1.16.162): cuando la brecha de Keiko supera una brecha
 *  histórica, el sublabel muta a "Votos (> Keiko vs. PPK)" y el widget
 *  "palpita" en cada refresh/recarga. Clases is-hito / is-palpito = race-header.js.
 * =========================================================================*/
.race-header__threshold.is-hito .race-header__brecha-unit {
  text-transform: none;
  font-size: 9px;
  letter-spacing: 0;
  font-weight: var(--fw-bold);
  color: var(--c-party-fp);
  white-space: nowrap;
}
@keyframes rhPalpito {
  0%, 100% { transform: scale(1); }
  22%      { transform: scale(1.07); }
  45%      { transform: scale(0.985); }
  68%      { transform: scale(1.035); }
}
.race-header__threshold.is-palpito {
  animation: rhPalpito 0.9s ease-in-out;
}

/* ===========================================================================
 *  Responsive (v0.1.12.2 — refinamiento de feedback de Renzo)
 *
 *  Política Opción B: no se toca nada en desktop. Las reglas globales
 *  arriba siguen definiendo el race-header tal cual está en PC.
 *
 *  Historia:
 *    · v0.1.12.0 intentó layout vertical en ≤480px (brecha arriba,
 *      candidatos en filas abajo). Renzo rechazó ese enfoque: prefiere
 *      mantener la "imagen de tirar la soga" (versus horizontal) que
 *      sí cabe limpio en el panel detalle inferior (.nav-detail__versus
 *      con avatar 56px, % 28px, nombres 13px).
 *    · v0.1.12.2 revierte a HORIZONTAL en todos los anchos. Las
 *      proporciones del versus inferior se replican aquí: avatar más
 *      chico, % más chico, brecha min-width más chico. Resultado: todo
 *      cabe en una línea sin wrapping hasta anchos muy estrechos.
 * =========================================================================*/

@media (max-width: 768px) {
  /* v0.1.13.2 — Layout columna dentro de cada lado:
        [foto avatar]
        [%]
        [nombre]
        [partido]

     El orden de aparición es el de append en race-header.js:
        side > wrap (avatar)
        side > info > delta + pct + name + party

     Con flex-direction: column en side e info, los elementos quedan
     en ese orden vertical natural. Sin necesidad de `order:`.

     Las % se reducen al 80% del tamaño que tenía v0.1.13.1 (1.75rem →
     1.4rem ≈ 22px). El % sigue siendo el ancla visual del lado, pero
     ya no compite con la foto. */
  .race-header {
    --race-avatar-size: 95px;     /* v0.1.13.4: +30% sobre 73 (= 73*1.3). Renzo pidió fotos más grandes otra vez. */
    --race-pct-size:    1.12rem;  /* v0.1.13.3: 80% de 1.4rem ≈ 18px. Renzo pidió % más chico aún. */
  }
  .race-header__top { gap: var(--sp-2); }

  /* Cada side pasa a columna vertical: avatar arriba, info debajo. */
  .race-header__side,
  .race-header__side--left,
  .race-header__side--right {
    flex-direction: column;
    align-items: center;
    gap: var(--sp-1);
    flex-direction: column !important;  /* override del row-reverse del right */
  }
  /* Info en columna: pct, name, party. */
  .race-header__info {
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: 2px;
  }
  .race-header__side--left  .race-header__info,
  .race-header__side--right .race-header__info {
    text-align: center;
  }
  /* Delta (flechas de movimiento) en horizontal arriba. */
  .race-header__delta {
    justify-content: center !important;
  }
  .race-header__side--left  .race-header__delta,
  .race-header__side--right .race-header__delta {
    justify-content: center;
  }

  .race-header__threshold {
    min-width: 90px;
  }
  .race-header__brecha-val   { font-size: 18px; }
  .race-header__brecha-label,
  .race-header__brecha-live  { font-size: 10px; }
  .race-header__name         { font-size: 13px; }
  .race-header__party        { font-size: 10px; }

  /* Barra de votos más delgada en mobile. */
  .race-header__bar          { height: 24px; }   /* v0.1.13.2: 28 → 24 */
  .race-header__bar-votos    { font-size: 10px; }
  .race-header__bar-diff     { font-size: 9px; padding: 1px var(--sp-1); }

  /* Botón Refrescar más delgado en mobile. */
  .race-header__refresh {
    padding: 6px 16px;             /* v0.1.13.2: era 9px 18px en desktop */
    font-size: 12px;
  }
}

@media (max-width: 480px) {
  /* Mobile chico (iPhone SE / Android pequeño). Sin layout vertical
     adicional — el column del @media 768 ya cubre la presentación. */
  .race-header {
    --race-avatar-size: 74px;     /* v0.1.13.4: +30% sobre 57 (= 57*1.3) */
    --race-pct-size:    0.96rem;  /* v0.1.13.3: 80% de 1.2rem ≈ 15.4px */
  }
  .race-header__threshold {
    min-width: 70px;
  }
  .race-header__brecha-val   { font-size: 16px; }
  .race-header__brecha-label,
  .race-header__brecha-live  { font-size: 9px; }
  .race-header__name         { font-size: 12px; }
  .race-header__party        { font-size: 9px; }

  /* Botón refrescar: full-width en mobile chico pero SIN min-height
     forzado (decisión 4 de Renzo: más delgado, no abultado). */
  .race-header__refresh {
    width: 100%;
    justify-content: center;
    padding: 5px 14px;
  }
}
