/* ==========================================================================
   UNOi — Capa premium de micro-interacciones y transiciones
   static/css/animaciones.css  ·  CSS puro, sin build, sin dependencias.

   PRINCIPIOS (no negociables):
   · Cero scroll-jacking: aquí no hay pin, ni sticky, ni nada que mida scroll.
   · Cero contenido oculto: esta hoja NUNCA pone opacity:0 a contenido que no
     tenga ya un camino garantizado para volver a ser visible (el sistema de
     reveals de main.js y sus redes de seguridad). Solo REFINA lo existente.
   · Todo efecto de puntero vive bajo @media (hover:hover) and (pointer:fine).
   · El bloque final @media (prefers-reduced-motion: reduce) neutraliza TODO.

   NOTA DE CASCADA: el Tailwind del CDN inyecta su <style> en runtime y puede
   quedar DESPUÉS de este archivo. Por eso las reglas que compiten con una
   utilidad de Tailwind usan selectores de especificidad MAYOR (p. ej.
   "a.group:hover" > ".hover\:shadow-xl:hover"), no confían en el orden.
   ========================================================================== */

:root {
  /* Curva con carácter: arranca decidida y asienta suave (overshoot leve) */
  --ease-unoi: cubic-bezier(.16, 1, .3, 1);
  /* Curva neutra para micro-feedback (pulsaciones, focus) */
  --ease-corta: cubic-bezier(.4, 0, .2, 1);
  --dur-reveal: .7s;
  --dur-hover: .45s;
  --dur-tacto: .2s;
  /* Solo los dos tonos que se usan en degradados de subrayado. Las sombras
     llevan la tinta en rgba() literal (rgba() no admite un var hexadecimal). */
  --azul-unoi: #16ABE2;
  --verde-unoi: #75B547;
}

/* ==========================================================================
   1 · TRANSICIÓN ENTRE PÁGINAS (View Transitions API multipágina)
   Nativa, sin JS. Donde no se soporte, el navegador ignora la at-rule y la
   navegación es la de siempre: degradación total y silenciosa.
   ========================================================================== */

@view-transition {
  navigation: auto;
}

/* Cross-fade corto: la página que se va se apaga, la que entra sube 6px. */
::view-transition-old(root) {
  animation: unoi-vt-salida 220ms var(--ease-corta) both;
}

::view-transition-new(root) {
  animation: unoi-vt-entrada 260ms var(--ease-unoi) both;
}

@keyframes unoi-vt-salida {
  to { opacity: 0; }
}

@keyframes unoi-vt-entrada {
  from { opacity: 0; translate: 0 6px; }
}

/* ==========================================================================
   2 · REVEALS — refinamiento (NO se toca el mecanismo)
   El mecanismo sigue siendo el de base.html + main.js:
   .js-reveal → JS añade .reveal-init (oculta) y luego .reveal-in (muestra),
   con red de seguridad a los 2.5s y en pagehide. Aquí solo se afina el
   "cómo" del movimiento: un desenfoque mínimo que se resuelve y una escala
   casi imperceptible.

   · Clases repetidas (.reveal-init.reveal-init) = misma clase, más
     especificidad. Así estas reglas ganan al <style> de base.html
     independientemente del orden de carga.
   · Se usan longhands de transition (NO el shorthand) para no pisar los
     transition-delay del escalonado.
   · El estado final es opacity:1 / transform:none / filter:none. Sin excepción.
   ========================================================================== */

.reveal-init.reveal-init > * {
  /* Estado oculto: idéntico al de base.html + blur sutil + escala mínima */
  opacity: 0;
  transform: translateY(28px) scale(.985);
  filter: blur(6px);
}

.reveal-init.reveal-in.reveal-in > * {
  /* Estado final: SIEMPRE visible y sin filtros */
  opacity: 1;
  transform: none;
  filter: none;
  /* La lista incluye translate y box-shadow ADEMÁS de las tres del reveal:
     muchas tarjetas son hijas directas de un .js-reveal, y esta regla (0,3,0)
     gana a las de hover (0,2,1). Si no estuvieran aquí, esas tarjetas se
     elevarían de golpe, sin transición. Duraciones posicionales: reveal .7s,
     hover .45s. */
  transition-property: opacity, transform, filter, translate, box-shadow;
  transition-duration: var(--dur-reveal), var(--dur-reveal), var(--dur-reveal), var(--dur-hover), var(--dur-hover);
  transition-timing-function: var(--ease-unoi);
}

/* Escalonado propio (extiende el de base.html hasta 6 hijos).
   Especificidad (0,4,0) → gana siempre a ".reveal-init.reveal-in > :nth-child(n)".
   El retardo se declara como lista posicional: solo se escalona el reveal;
   translate y box-shadow (el hover) van SIEMPRE a 0s, o el ratón notaría
   un retraso al pasar por las tarjetas 2ª, 3ª, 4ª… */
.reveal-init.reveal-in.reveal-in > :nth-child(2) { transition-delay: .07s, .07s, .07s, 0s, 0s; }
.reveal-init.reveal-in.reveal-in > :nth-child(3) { transition-delay: .14s, .14s, .14s, 0s, 0s; }
.reveal-init.reveal-in.reveal-in > :nth-child(4) { transition-delay: .21s, .21s, .21s, 0s, 0s; }
.reveal-init.reveal-in.reveal-in > :nth-child(5) { transition-delay: .28s, .28s, .28s, 0s, 0s; }
.reveal-init.reveal-in.reveal-in > :nth-child(6) { transition-delay: .35s, .35s, .35s, 0s, 0s; }

/* ==========================================================================
   3 · NAV — estado .nav-scrolled (la clase la añade un módulo JS)
   La isla se compacta ~4px por lado, el blanco se vuelve casi opaco y la
   sombra gana profundidad. Sin cambios de tamaño de tipografía → sin saltos.
   ========================================================================== */

/* Base: la isla siempre sabe transicionar (también al volver arriba). */
header > div {
  transition-property: padding-top, padding-bottom, background-color, box-shadow, border-color, backdrop-filter;
  transition-duration: .3s;
  transition-timing-function: var(--ease-corta);
}

/* Se contemplan las dos formas de cablearlo: clase en el <header> o en la isla. */
header.nav-scrolled > div,
header > div.nav-scrolled {
  padding-top: .5rem;    /* py-3 = .75rem */
  padding-bottom: .5rem;
  background-color: rgba(255, 255, 255, .97);
  border-color: rgba(23, 39, 43, .07);
  box-shadow: 0 10px 30px -12px rgba(23, 39, 43, .22), 0 2px 6px -2px rgba(23, 39, 43, .06);
  backdrop-filter: blur(14px) saturate(150%);
  -webkit-backdrop-filter: blur(14px) saturate(150%);
}

/* ==========================================================================
   4 · HOVER PREMIUM EN TARJETAS (solo puntero fino)
   Se usa la propiedad individual "translate" en vez de "transform": así no se
   pisa ningún transform de Tailwind (group-hover:scale-105) ni de GSAP.
   ========================================================================== */

@media (hover: hover) and (pointer: fine) {

  /* Tarjetas-enlace (.group) y tarjetas de contenido (articles redondeados).
     El <article> del post de blog no lleva rounded-* → queda fuera. */
  a.group,
  article[class*="rounded-2xl"],
  article[class*="rounded-3xl"] {
    transition-property: translate, box-shadow, background-color, border-color;
    transition-duration: var(--dur-hover);
    transition-timing-function: var(--ease-unoi);
  }

  a.group:hover,
  article[class*="rounded-2xl"]:hover,
  article[class*="rounded-3xl"]:hover {
    translate: 0 -6px;
    /* Sombra más rica y más "lejos" del papel; tinta de marca, nunca negro puro */
    box-shadow: 0 26px 48px -26px rgba(23, 39, 43, .30), 0 6px 14px -8px rgba(23, 39, 43, .10);
  }

  /* CONVIVENCIA CON premium.js: ese módulo aplica un tilt 3D (transform +
     transition EN LÍNEA) a ".js-reveal > article", ".js-reveal > figure",
     ".js-reveal > a.rounded-3xl" y ".js-reveal > div.rounded-3xl". En esas
     tarjetas se renuncia al desplazamiento: sumar elevación al tilt duplica
     el movimiento y, como su transition en línea no incluye "translate",
     la elevación daría un salto seco. La sombra rica SÍ se conserva: ellos
     dejan box-shadow al CSS y lo incluyen en su transition en línea.
     Va después de la regla de :hover para ganarle por orden (misma
     especificidad, 0-2-1). */
  .js-reveal > article:hover,
  .js-reveal > figure:hover,
  .js-reveal > a.rounded-3xl:hover,
  .js-reveal > div.rounded-3xl:hover {
    translate: none;
  }

  /* Círculo blanco del icono: escala y giro levísimos al pasar por la tarjeta.
     Base primero (para que también vuelva con suavidad). */
  article[class*="rounded-2xl"] > span[class*="rounded-full"],
  article[class*="rounded-3xl"] > span[class*="rounded-full"] {
    transition-property: scale, rotate, box-shadow;
    transition-duration: .5s;
    transition-timing-function: var(--ease-unoi);
  }

  article[class*="rounded-2xl"]:hover > span[class*="rounded-full"],
  article[class*="rounded-3xl"]:hover > span[class*="rounded-full"] {
    scale: 1.08;
    rotate: -6deg;
  }

  /* En las tarjetas-enlace solo se anima el círculo que contiene un icono SVG
     (así no se toca la píldora de categoría "DIRECTORES/DOCENTES/...").
     :has() va en su propio bloque: si el navegador no lo soporta, solo se
     descarta esta regla y el resto sigue vivo. */
  a.group span[class*="rounded-full"]:has(> svg) {
    transition: scale .5s var(--ease-unoi), rotate .5s var(--ease-unoi);
  }

  a.group:hover span[class*="rounded-full"]:has(> svg) {
    scale: 1.08;
    rotate: -6deg;
  }
}

/* ==========================================================================
   5 · BOTONES — barrido de luz + tacto al pulsar
   El barrido se acota a <a> y <button> con fondo de gradiente: .bg-unoi
   también se usa en la barra de progreso, avatares y viñetas, que NO deben
   llevar pseudo-elemento.
   ========================================================================== */

@media (hover: hover) and (pointer: fine) {

  a.bg-unoi,
  button.bg-unoi {
    position: relative;
    overflow: hidden;
    /* isolation + z-index:-1 en el pseudo = el brillo pasa por encima del
       degradado pero por DEBAJO del texto: el texto ni se mueve ni se lava. */
    isolation: isolate;
  }

  a.bg-unoi::after,
  button.bg-unoi::after {
    content: "";
    position: absolute;
    inset: 0;
    z-index: -1;
    pointer-events: none;
    background-image: linear-gradient(100deg,
      rgba(255, 255, 255, 0) 38%,
      rgba(255, 255, 255, .55) 50%,
      rgba(255, 255, 255, 0) 62%);
    translate: -130% 0;
  }

  a.bg-unoi:hover::after,
  button.bg-unoi:hover::after,
  a.bg-unoi:focus-visible::after,
  button.bg-unoi:focus-visible::after {
    animation: unoi-brillo-boton .85s var(--ease-corta);
  }
}

@keyframes unoi-brillo-boton {
  from { translate: -130% 0; }
  to   { translate: 130% 0; }
}

/* Tacto al pulsar: fuera del media query de hover (en táctil :active sí ocurre).
   Se usa "scale" (propiedad individual) para no romper el transform que GSAP
   escribe en los .js-magnetico ni los scale-* de Tailwind. */
a[class*="rounded-full"]:not(.js-magnetico),
a.bg-unoi:not(.js-magnetico),
button:not(.js-magnetico) {
  transition-property: scale, transform, box-shadow, background-color, border-color, color, opacity;
  transition-duration: var(--dur-tacto);
  transition-timing-function: var(--ease-corta);
}

/* Los magnéticos llevan transform reescrito cada frame por GSAP:
   transicionar "transform" aquí lo volvería pastoso. Solo "scale". */
a.js-magnetico {
  transition-property: scale, box-shadow, background-color, border-color, color;
  transition-duration: var(--dur-tacto);
  transition-timing-function: var(--ease-corta);
}

a[class*="rounded-full"]:active,
a.bg-unoi:active,
a.js-magnetico:active,
button:active {
  scale: .97;
}

/* ==========================================================================
   6 · ENLACES DE TEXTO — subrayado que crece desde la izquierda
   background-image + background-size animado (no toca el layout).
   ========================================================================== */

.enlace-animado {
  background-image: linear-gradient(90deg, var(--azul-unoi) 0%, var(--verde-unoi) 100%);
  background-repeat: no-repeat;
  background-position: 0 100%;
  background-size: 0% 2px;
  text-decoration: none;
  transition: background-size .45s var(--ease-unoi);
}

.enlace-animado:hover,
.enlace-animado:focus-visible {
  background-size: 100% 2px;
}

/* Aplicación automática y CONSERVADORA: solo enlaces dentro de <p>, y solo si
   no parecen botón (sin bg-*, sin rounded-*, sin border-*, sin .js-magnetico).
   Todo el filtro va dentro de :where() → especificidad (0,0,2), así cualquier
   regla posterior puede desactivarlo sin pelear. */
p a:where(:not([class*="bg-"]):not([class*="rounded"]):not([class*="border"]):not(.js-magnetico)) {
  background-image: linear-gradient(90deg, var(--azul-unoi) 0%, var(--verde-unoi) 100%);
  background-repeat: no-repeat;
  background-position: 0 100%;
  background-size: 0% 2px;
  transition: background-size .45s var(--ease-unoi);
}

p a:where(:not([class*="bg-"]):not([class*="rounded"]):not([class*="border"]):not(.js-magnetico)):hover,
p a:where(:not([class*="bg-"]):not([class*="rounded"]):not([class*="border"]):not(.js-magnetico)):focus-visible {
  background-size: 100% 2px;
}

/* En el cuerpo del blog (.prose) los enlaces ya van subrayados por el plugin
   typography: se desactiva el subrayado animado para no duplicarlo. */
.prose p a {
  background-image: none;
}

/* ==========================================================================
   7 · BLOBS VIVOS — los halos decorativos respiran
   Solo los blobs: .rounded-full + .blur-3xl dentro de un envoltorio
   aria-hidden="true". Los círculos sólidos decorativos (sin blur-3xl) no
   entran. Movimiento casi subliminal, muy lento y desincronizado.
   ========================================================================== */

[aria-hidden="true"] .rounded-full.blur-3xl {
  animation: unoi-respirar 24s ease-in-out infinite alternate;
}

/* Delays negativos: cada blob arranca ya en otro punto del ciclo (nunca al
   unísono) y sin periodo inicial estático. */
[aria-hidden="true"] .rounded-full.blur-3xl:nth-child(2) {
  animation-duration: 29s;
  animation-delay: -8s;
}

[aria-hidden="true"] .rounded-full.blur-3xl:nth-child(3) {
  animation-duration: 34s;
  animation-delay: -17s;
}

@keyframes unoi-respirar {
  from { translate: 0 0;        scale: 1; }
  to   { translate: 2% -2.5%;   scale: 1.07; }
}

/* ==========================================================================
   8 · BRILLO DEL GRADIENTE DE MARCA
   El degradado de los textos con bg-clip-text se desplaza muy despacio.
   El fondo se agranda solo un 20%: el texto SIEMPRE queda cubierto por color
   (nunca transparente) y nunca se aleja de la paleta original.
   ========================================================================== */

.bg-unoi-logo.bg-clip-text,
.bg-unoi.bg-clip-text {
  background-size: 120% 100%;
  animation: unoi-brillo-marca 11s ease-in-out infinite alternate;
}

@keyframes unoi-brillo-marca {
  from { background-position: 0% 50%; }
  to   { background-position: 100% 50%; }
}

/* ==========================================================================
   9 · DETALLES FINOS — foco accesible y swap de HTMX
   ========================================================================== */

/* Anillo de marca en :focus-visible. El selector de elementos va en :where()
   → especificidad (0,1,0), por debajo de las utilidades focus:* de Tailwind
   (0,2,0): los inputs del formulario conservan su focus:ring-2 intacto. */
:where(a, button, summary, [tabindex], input, select, textarea):focus-visible {
  outline: 2px solid var(--azul-unoi);
  outline-offset: 3px;
  transition: outline-offset .18s var(--ease-corta), outline-color .18s var(--ease-corta);
}

/* HTMX (formulario de leads en #lead-box).
   Se usan ANIMACIONES con fill "both", no estados persistentes: aunque una
   clase de htmx se quedara pegada, el contenido termina visible. La salida
   además nunca llega a opacity:0 (se queda en .12) para no poder "borrar"
   el formulario si una petición fallara a mitad. */
.htmx-swapping {
  animation: unoi-htmx-salida .18s var(--ease-corta) both;
}

.htmx-added {
  animation: unoi-htmx-entrada .34s var(--ease-unoi) both;
}

form.htmx-request {
  opacity: .72;
  cursor: progress;
  transition: opacity .2s var(--ease-corta);
}

@keyframes unoi-htmx-salida {
  to { opacity: .12; transform: translateY(-4px); }
}

@keyframes unoi-htmx-entrada {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: none; }
}

/* ==========================================================================
   ÚLTIMO BLOQUE · prefers-reduced-motion
   Neutraliza TODO lo anterior. Se usa !important solo aquí y solo para
   garantizar que ninguna regla de arriba (con más especificidad) deje algo
   en movimiento o, peor, desenfocado/oculto.
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {

  /* 1 · Sin transición entre páginas */
  @view-transition {
    navigation: none;
  }

  ::view-transition-old(root),
  ::view-transition-new(root) {
    animation: none !important;
  }

  /* 2 · Reveals: contenido visible, nítido y quieto en cualquier estado */
  .reveal-init > *,
  .reveal-init.reveal-init > *,
  .reveal-init.reveal-in.reveal-in > * {
    opacity: 1 !important;
    transform: none !important;
    filter: none !important;
    transition: none !important;
    animation: none !important;
  }

  /* 3 · Nav: el cambio de estado ocurre, pero sin animarlo */
  header > div,
  header.nav-scrolled > div,
  header > div.nav-scrolled {
    transition: none !important;
  }

  /* 4 · Tarjetas: se conserva el cambio de sombra, se anula el movimiento */
  a.group,
  a.group:hover,
  article[class*="rounded-2xl"],
  article[class*="rounded-2xl"]:hover,
  article[class*="rounded-3xl"],
  article[class*="rounded-3xl"]:hover,
  article[class*="rounded-2xl"] > span[class*="rounded-full"],
  article[class*="rounded-2xl"]:hover > span[class*="rounded-full"],
  article[class*="rounded-3xl"] > span[class*="rounded-full"],
  article[class*="rounded-3xl"]:hover > span[class*="rounded-full"] {
    translate: none !important;
    scale: none !important;
    rotate: none !important;
    transition: none !important;
  }

  /* 5 · Botones: sin barrido y sin rebote al pulsar */
  a.bg-unoi::after,
  button.bg-unoi::after,
  a.bg-unoi:hover::after,
  button.bg-unoi:hover::after,
  a.bg-unoi:focus-visible::after,
  button.bg-unoi:focus-visible::after {
    animation: none !important;
    opacity: 0 !important;
  }

  a[class*="rounded-full"],
  a[class*="rounded-full"]:active,
  a.bg-unoi,
  a.bg-unoi:active,
  a.js-magnetico,
  a.js-magnetico:active,
  button,
  button:active {
    scale: none !important;
    transition: none !important;
  }

  /* 6 · Enlaces: el subrayado aparece, pero sin crecer */
  .enlace-animado,
  p a {
    transition: none !important;
  }

  /* 7 · Blobs quietos */
  [aria-hidden="true"] .rounded-full.blur-3xl {
    animation: none !important;
  }

  /* 8 · Gradiente de marca: se restaura el encuadre original, sin shimmer */
  .bg-unoi-logo.bg-clip-text,
  .bg-unoi.bg-clip-text {
    animation: none !important;
    background-size: 100% 100% !important;
    background-position: 0 0 !important;
  }

  /* 9 · Foco y HTMX sin animación (el contenido queda visible por defecto) */
  :where(a, button, summary, [tabindex], input, select, textarea):focus-visible {
    transition: none !important;
  }

  .htmx-swapping,
  .htmx-added {
    animation: none !important;
    opacity: 1 !important;
    transform: none !important;
  }

  form.htmx-request {
    transition: none !important;
  }
}

/* ==========================================================================
   10 · HERO — capa "wow": halo giratorio, sheen, órbitas y chip de vidrio
   Todo es decorativo y aditivo: si algo de esto no se pinta, el hero sigue
   completo (la foto/el video nunca dependen de estas reglas).
   ========================================================================== */

/* Halo: cónico de marca girando lentísimo, difuminado detrás del marco */
.hero-halo {
  background: conic-gradient(from 0deg, #16ABE2, #75B547, #F7C331, #16ABE2);
  filter: blur(38px);
  opacity: .28;
  animation: hero-halo-giro 26s linear infinite;
  z-index: 0;
}
@keyframes hero-halo-giro { to { transform: rotate(360deg); } }

.hero-marco { z-index: 1; }

/* Sheen: destello que cruza el marco cada cierto tiempo */
.hero-sheen {
  background: linear-gradient(105deg, transparent 35%, rgba(255,255,255,.42) 50%, transparent 65%);
  transform: translateX(-120%);
  animation: hero-sheen-pasa 9s ease-in-out 2.5s infinite;
}
@keyframes hero-sheen-pasa {
  0%   { transform: translateX(-120%); }
  22%  { transform: translateX(120%); }
  100% { transform: translateX(120%); }
}

/* Órbitas: los círculos de acento flotan desfasados, muy suave */
.hero-orbita-1 { animation: hero-flota-1 11s ease-in-out infinite; z-index: 2; }
.hero-orbita-2 { animation: hero-flota-2 9s ease-in-out infinite; z-index: 2; }
@keyframes hero-flota-1 {
  0%, 100% { transform: translate3d(0,0,0); }
  50%      { transform: translate3d(6px,-12px,0); }
}
@keyframes hero-flota-2 {
  0%, 100% { transform: translate3d(0,0,0); }
  50%      { transform: translate3d(-8px,10px,0); }
}

/* Chip de vidrio: entra al cargar y respira apenas */
.hero-chip {
  z-index: 3;
  animation: hero-chip-entra .8s cubic-bezier(.16,1,.3,1) .5s both,
             hero-chip-flota 7s ease-in-out 1.3s infinite;
}
@keyframes hero-chip-entra {
  from { opacity: 0; transform: translateY(14px) scale(.96); }
  to   { opacity: 1; transform: none; }
}
@keyframes hero-chip-flota {
  0%, 100% { translate: 0 0; }
  50%      { translate: 0 -6px; }
}

/* Cuando el video está listo, aparece y la foto se desvanece por debajo */
.hero-marco.video-activo .js-hero-video { opacity: 1; }
.hero-marco.video-activo .js-hero-foto  { opacity: 0; }

@media (prefers-reduced-motion: reduce) {
  .hero-halo, .hero-sheen, .hero-orbita-1, .hero-orbita-2, .hero-chip { animation: none !important; }
  .hero-chip { opacity: 1 !important; transform: none !important; }
  .hero-sheen { display: none; }
}

/* ==========================================================================
   11 · HERO — bolitas de color a la deriva
   Cada .bolita se posiciona con --x/--y y se colorea/dimensiona con --c/--s.
   Tres patrones (a, b, c) con duraciones (--d) y desfases (--r) distintos
   para que nunca se muevan en bloque. Solo transform y opacity: compositable.
   ========================================================================== */
.hero-bolitas .bolita {
  position: absolute;
  left: var(--x);
  top: var(--y);
  width: var(--s);
  height: var(--s);
  border-radius: 9999px;
  background: var(--c);
  opacity: .5;
  will-change: transform;
  animation-duration: var(--d, 22s);
  animation-delay: var(--r, 0s);
  animation-timing-function: ease-in-out;
  animation-iteration-count: infinite;
}

/* Patrón A: vaivén vertical amplio con ligera deriva lateral */
.bolita-a { animation-name: bolita-deriva-a; }
@keyframes bolita-deriva-a {
  0%, 100% { transform: translate3d(0, 0, 0) scale(1); opacity: .5; }
  35%      { transform: translate3d(14px, -26px, 0) scale(1.12); opacity: .75; }
  70%      { transform: translate3d(-10px, -12px, 0) scale(.92); opacity: .38; }
}

/* Patrón B: órbita suave en ocho */
.bolita-b { animation-name: bolita-deriva-b; }
@keyframes bolita-deriva-b {
  0%, 100% { transform: translate3d(0, 0, 0) scale(1); opacity: .45; }
  25%      { transform: translate3d(-18px, -14px, 0) scale(1.08); opacity: .7; }
  50%      { transform: translate3d(-6px, 22px, 0) scale(.95); opacity: .5; }
  75%      { transform: translate3d(16px, 6px, 0) scale(1.05); opacity: .65; }
}

/* Patrón C: ascenso lento con respiración (las más pequeñas) */
.bolita-c { animation-name: bolita-deriva-c; }
@keyframes bolita-deriva-c {
  0%, 100% { transform: translate3d(0, 0, 0) scale(1); opacity: .4; }
  50%      { transform: translate3d(8px, -34px, 0) scale(1.18); opacity: .72; }
}

@media (prefers-reduced-motion: reduce) {
  .hero-bolitas .bolita { animation: none !important; opacity: .4; }
}
