/* ============================================================
   TuCelebración · tc-fx.css  (v2.0)
   ------------------------------------------------------------
   MOTOR DE EFECTOS ANIMADOS — 100% CSS, sin canvas.

   Estructura que inyecta tc-fx-engine.js:

     <div class="jcl-fx" data-fx="petalos" aria-hidden="true">
       <i style="--x:34; --d:11; --dl:-3; --s:.8; --o:.6; --r:120"></i>
       ...
     </div>

   Cada <i> es SOLO un carril de movimiento (cae, sube o deriva).
   La figura visible se dibuja en i::before, que lleva su propia
   animación secundaria (balanceo, giro, aleteo). Separarlas
   permite combinar dos movimientos sin JS.

   REGLA DE PERFORMANCE
   Solo se animan `transform` y `opacity` — las dos propiedades
   que el navegador resuelve en la GPU sin recalcular layout.
   `filter: blur()` es caro, así que queda reservado a los cuatro
   efectos de conteo bajo (bokeh, bruma, aurora, rayos).
   ============================================================ */

.jcl-fx{
  position:absolute;
  inset:0;
  overflow:hidden;
  pointer-events:none;
  /* Por encima del contenido: las partículas tienen que pasar por
     delante del texto y de la foto, como en una invitación impresa
     con brillos. No interceptan clics. */
  z-index:7;
  border-radius:inherit;
  /* `strict` incluye size containment y puede colapsar una capa absoluta
     insertada en una tarjeta con container queries. Solo necesitamos aislar
     pintura y layout. */
  contain:layout paint;
}

/* Cada <i> es un punto sin dimensiones: solo marca dónde está la
   partícula. Como mide 0×0, ningún tamaño en porcentaje funciona
   dentro suyo — por eso las figuras se dimensionan con las
   variables `--track` (alto de la tarjeta) y `--tw` (ancho), que
   escribe el motor en píxeles. */
.jcl-fx i{
  position:absolute;
  top:0;
  left:calc(var(--x, 50) * 1%);
  display:block;
  width:0; height:0;
  opacity:var(--o, .8);
  animation-duration:calc(var(--d, 10) * 1s);
  animation-delay:calc(var(--dl, 0) * 1s);
  animation-timing-function:linear;
  animation-iteration-count:infinite;
  animation-fill-mode:both;
}

.jcl-fx i::before{
  content:"";
  position:absolute;
  display:block;
  transform-origin:center;
  animation-duration:calc(var(--d2, 3) * 1s);
  animation-delay:calc(var(--dl, 0) * 1s);
  animation-timing-function:ease-in-out;
  animation-iteration-count:infinite;
}

/* Paleta del efecto. `--jcl-fx-color` la resuelve el motor según el
   `tone` del catálogo: acento del template, blanco, o multicolor. */
.jcl-fx{
  --jcl-fx-color: var(--tc-primary, #d4af37);
  --jcl-fx-color-2: #ffffff;
}
.jcl-fx[data-tone="light"]{ --jcl-fx-color:#ffffff; }


/* ============================================================
   CARRILES DE MOVIMIENTO
   ============================================================ */

/* Caída vertical, de arriba del borde hasta abajo del borde. */
@keyframes jclFall{
  0%   { transform: translate3d(0, -12%, 0); }
  100% { transform: translate3d(0, calc(var(--track, 460) * 1px), 0); }
}

/* Ascenso. */
@keyframes jclRise{
  0%   { transform: translate3d(0, calc(var(--track, 460) * 1px), 0); }
  100% { transform: translate3d(0, -12%, 0); }
}

/* Deriva libre: sube en diagonal y vuelve, sin salir del marco. */
@keyframes jclDrift{
  0%   { transform: translate3d(0, calc(var(--track, 460) * 1px * .82), 0); }
  50%  { transform: translate3d(calc(var(--dx, 40) * 1px), calc(var(--track, 460) * 1px * .28), 0); }
  100% { transform: translate3d(0, calc(var(--track, 460) * 1px * .82), 0); }
}

/* Vuelo en S, de un lado al otro de la tarjeta.
   El recorrido se mide contra el ancho real (`--tw`) y no contra el
   viewport: una mariposa que cruzara 100vw dentro de una tarjeta de
   340px se iría de pantalla en el primer segundo. */
@keyframes jclCross{
  0%   { transform: translate3d(calc(var(--tw, 340) * -.18px), calc(var(--track, 460) * .70px), 0); }
  25%  { transform: translate3d(calc(var(--tw, 340) * .18px),  calc(var(--track, 460) * .40px), 0); }
  50%  { transform: translate3d(calc(var(--tw, 340) * .48px),  calc(var(--track, 460) * .60px), 0); }
  75%  { transform: translate3d(calc(var(--tw, 340) * .76px),  calc(var(--track, 460) * .30px), 0); }
  100% { transform: translate3d(calc(var(--tw, 340) * 1.12px), calc(var(--track, 460) * .50px), 0); }
}

/* Estático: la figura no se traslada, solo late en su lugar. */
@keyframes jclHold{
  0%,100% { transform: translate3d(0, calc(var(--track, 460) * 1px * var(--y, .5)), 0); }
}


/* ============================================================
   MOVIMIENTOS SECUNDARIOS (sobre i::before)
   ============================================================ */

@keyframes jclSway{
  0%,100% { transform: translateX(calc(var(--sw, 14) * -1px)) rotate(calc(var(--r, 0) * 1deg)); }
  50%     { transform: translateX(calc(var(--sw, 14) * 1px)) rotate(calc(var(--r, 0) * 1deg + 24deg)); }
}

@keyframes jclTumble{
  0%   { transform: rotate3d(1, 1, .4, 0deg); }
  100% { transform: rotate3d(1, 1, .4, 720deg); }
}

@keyframes jclSpin{
  0%   { transform: rotate(calc(var(--r, 0) * 1deg)); }
  100% { transform: rotate(calc(var(--r, 0) * 1deg + 360deg)); }
}

@keyframes jclBlink{
  0%,100% { opacity:0; transform:scale(.35); }
  40%     { opacity:1; transform:scale(1); }
  70%     { opacity:.5; transform:scale(.85); }
}

@keyframes jclTwinkle{
  0%,100% { opacity:.15; transform:scale(.5) rotate(0deg); }
  50%     { opacity:1;   transform:scale(1) rotate(45deg); }
}

@keyframes jclPulse{
  0%,100% { opacity:.30; transform:scale(.92); }
  50%     { opacity:.72; transform:scale(1.08); }
}

@keyframes jclWobble{
  0%,100% { transform: translateX(-7px) scale(1); }
  50%     { transform: translateX(7px) scale(1.06); }
}

@keyframes jclFlapL{
  0%,100% { transform: rotateY(0deg); }
  50%     { transform: rotateY(72deg); }
}
@keyframes jclFlapR{
  0%,100% { transform: rotateY(0deg); }
  50%     { transform: rotateY(-72deg); }
}


/* ============================================================
   1 · LUCIÉRNAGAS — punto cálido con halo, deriva y parpadeo
   ============================================================ */
.jcl-fx[data-fx="luciernagas"] i{ animation-name: jclDrift; animation-timing-function: ease-in-out; }
.jcl-fx[data-fx="luciernagas"] i::before{
  width:calc(var(--s, 1) * 5px);
  height:calc(var(--s, 1) * 5px);
  border-radius:50%;
  background: var(--jcl-fx-color);
  box-shadow:
    0 0 calc(var(--s, 1) * 8px) calc(var(--s, 1) * 2px) var(--jcl-fx-color),
    0 0 calc(var(--s, 1) * 22px) calc(var(--s, 1) * 4px) color-mix(in srgb, var(--jcl-fx-color) 45%, transparent);
  animation-name: jclBlink;
}

/* ============================================================
   2 · POLVO ESTELAR — grano fino que deriva y titila
   ============================================================ */
.jcl-fx[data-fx="polvo_estelar"] i{ animation-name: jclDrift; animation-timing-function: ease-in-out; }
.jcl-fx[data-fx="polvo_estelar"] i::before{
  width:calc(var(--s, 1) * 2px);
  height:calc(var(--s, 1) * 2px);
  border-radius:50%;
  background: var(--jcl-fx-color);
  box-shadow: 0 0 calc(var(--s, 1) * 5px) var(--jcl-fx-color);
  animation-name: jclBlink;
}

/* ============================================================
   3 · DESTELLOS — estrella de cuatro puntas que late
   ============================================================ */
.jcl-fx[data-fx="destellos"] i{ animation-name: jclHold; }
.jcl-fx[data-fx="destellos"] i::before{
  width:calc(var(--s, 1) * 16px);
  height:calc(var(--s, 1) * 16px);
  margin:calc(var(--s, 1) * -8px);
  background: var(--jcl-fx-color);
  clip-path: polygon(
    50% 0%, 58% 42%, 100% 50%, 58% 58%,
    50% 100%, 42% 58%, 0% 50%, 42% 42%
  );
  filter: drop-shadow(0 0 6px var(--jcl-fx-color));
  animation-name: jclTwinkle;
}

/* ============================================================
   4 · BOKEH — círculos desenfocados que suben lento
   ============================================================ */
.jcl-fx[data-fx="bokeh"] i{ animation-name: jclDrift; animation-timing-function: ease-in-out; }
.jcl-fx[data-fx="bokeh"] i::before{
  width:calc(var(--s, 1) * 46px);
  height:calc(var(--s, 1) * 46px);
  margin:calc(var(--s, 1) * -23px);
  border-radius:50%;
  background: radial-gradient(circle,
    color-mix(in srgb, var(--jcl-fx-color) 70%, transparent) 0%,
    color-mix(in srgb, var(--jcl-fx-color) 26%, transparent) 55%,
    transparent 72%);
  filter: blur(3px);
  animation-name: jclPulse;
}

/* ============================================================
   5 · RAYOS DE LUZ — haces inclinados que barren la portada
   ============================================================ */
/* Los haces arrancan siempre desde el borde superior (--y a 0): un
   rayo que empieza a mitad de la tarjeta no se lee como luz
   entrando, se lee como una mancha. */
.jcl-fx[data-fx="rayos_luz"] i{ animation-name: jclHold; top:0; --y: 0; }
.jcl-fx[data-fx="rayos_luz"] i::before{
  width:calc(var(--s, 1) * 90px);
  height:calc(var(--track, 460) * 1.7px);
  top:calc(var(--track, 460) * -.35px);
  margin-left:calc(var(--s, 1) * -45px);
  background: linear-gradient(180deg,
    transparent 0%,
    color-mix(in srgb, var(--jcl-fx-color) 48%, transparent) 30%,
    color-mix(in srgb, var(--jcl-fx-color) 22%, transparent) 65%,
    transparent 100%);
  filter: blur(10px);
  transform: rotate(16deg);
  animation-name: jclRayShift;
  animation-timing-function: ease-in-out;
}
@keyframes jclRayShift{
  0%,100% { opacity:.35; transform: rotate(16deg) translateX(-24px) scaleY(1); }
  50%     { opacity:.9;  transform: rotate(16deg) translateX(24px) scaleY(1.06); }
}

/* ============================================================
   6 · AURORA — bandas anchas que ondulan en el fondo
   ============================================================ */
.jcl-fx[data-fx="aurora"] i{ animation-name: jclHold; }
.jcl-fx[data-fx="aurora"] i::before{
  width:calc(var(--tw, 340) * 1.8px);
  height:calc(var(--s, 1) * 130px);
  margin-left:calc(var(--tw, 340) * -.9px);
  margin-top:calc(var(--s, 1) * -65px);
  border-radius:50%;
  background: linear-gradient(90deg,
    transparent 0%,
    color-mix(in srgb, var(--jcl-fx-color) 70%, transparent) 28%,
    color-mix(in srgb, var(--jcl-fx-color-2) 45%, transparent) 55%,
    color-mix(in srgb, var(--jcl-fx-color) 60%, transparent) 76%,
    transparent 100%);
  filter: blur(26px);
  animation-name: jclAuroraWave;
  animation-timing-function: ease-in-out;
}
@keyframes jclAuroraWave{
  0%,100% { opacity:.55; transform: translateX(-6%) skewY(-3deg) scaleY(1); }
  50%     { opacity:.95; transform: translateX(6%) skewY(3deg) scaleY(1.3); }
}

/* ============================================================
   7 · BRUMA — neblina baja, muy suave
   ============================================================ */
.jcl-fx[data-fx="bruma"] i{ animation-name: jclHold; }
.jcl-fx[data-fx="bruma"] i::before{
  width:calc(var(--s, 1) * 230px);
  height:calc(var(--s, 1) * 96px);
  margin:calc(var(--s, 1) * -48px) 0 0 calc(var(--s, 1) * -115px);
  border-radius:50%;
  background: radial-gradient(ellipse,
    color-mix(in srgb, var(--jcl-fx-color) 55%, transparent) 0%,
    color-mix(in srgb, var(--jcl-fx-color) 22%, transparent) 45%,
    transparent 72%);
  filter: blur(16px);
  animation-name: jclMistDrift;
  animation-timing-function: ease-in-out;
}
@keyframes jclMistDrift{
  0%,100% { opacity:.45; transform: translateX(-26px) scaleX(1); }
  50%     { opacity:.85; transform: translateX(26px) scaleX(1.18); }
}

/* ============================================================
   8 · ONDAS — anillos que se expanden y se desvanecen
   ============================================================ */
.jcl-fx[data-fx="ondas"] i{ animation-name: jclHold; }
.jcl-fx[data-fx="ondas"] i::before{
  width:calc(var(--s, 1) * 40px);
  height:calc(var(--s, 1) * 40px);
  margin:calc(var(--s, 1) * -20px);
  border-radius:50%;
  border:1px solid var(--jcl-fx-color);
  animation-name: jclRipple;
  animation-timing-function: ease-out;
}
@keyframes jclRipple{
  0%   { opacity:.75; transform: scale(.2); }
  100% { opacity:0;   transform: scale(3.2); }
}

/* ============================================================
   9 · PÉTALOS — caen girando y balanceándose
   ============================================================ */
.jcl-fx[data-fx="petalos"] i{ animation-name: jclFall; }
.jcl-fx[data-fx="petalos"] i::before{
  width:calc(var(--s, 1) * 13px);
  height:calc(var(--s, 1) * 16px);
  margin:calc(var(--s, 1) * -8px) 0 0 calc(var(--s, 1) * -6px);
  border-radius:100% 8% 100% 8%;
  background: linear-gradient(135deg,
    var(--jcl-fx-color) 0%,
    color-mix(in srgb, var(--jcl-fx-color) 55%, #fff) 100%);
  animation-name: jclSway;
}

/* ============================================================
   10 · HOJAS — caen dando vueltas sobre su eje
   ============================================================ */
.jcl-fx[data-fx="hojas"] i{ animation-name: jclFall; }
.jcl-fx[data-fx="hojas"] i::before{
  width:calc(var(--s, 1) * 15px);
  height:calc(var(--s, 1) * 9px);
  margin:calc(var(--s, 1) * -4px) 0 0 calc(var(--s, 1) * -7px);
  border-radius:0 100% 0 100%;
  background: linear-gradient(120deg,
    var(--jcl-fx-color) 0%,
    color-mix(in srgb, var(--jcl-fx-color) 40%, #2f5d3f) 100%);
  animation-name: jclTumble;
  animation-timing-function: linear;
}

/* ============================================================
   11 · MARIPOSAS — dos alas que aletean mientras cruzan
   ============================================================ */
.jcl-fx[data-fx="mariposas"]{ perspective: 300px; }
.jcl-fx[data-fx="mariposas"] i{
  animation-name: jclCross;
  animation-timing-function: ease-in-out;
  transform-style: preserve-3d;
}
/* Silueta completa como máscara en vez de dos alas hechas con
   border-radius: con dos rectángulos redondeados la mariposa se
   leía como dos manchas pegadas. El aleteo es un scaleX, que es
   exactamente lo que hace un ala vista de costado. */
.jcl-fx[data-fx="mariposas"] i::before{
  width:calc(var(--s, 1) * 22px);
  height:calc(var(--s, 1) * 20px);
  margin:calc(var(--s, 1) * -10px) 0 0 calc(var(--s, 1) * -11px);
  background: linear-gradient(150deg,
    var(--jcl-fx-color) 0%,
    color-mix(in srgb, var(--jcl-fx-color) 40%, #fff) 100%);
  -webkit-mask-image: var(--jcl-butterfly);
  mask-image: var(--jcl-butterfly);
  -webkit-mask-size: contain;
  mask-size: contain;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-position: center;
  mask-position: center;
  animation-name: jclFlutter;
}
.jcl-fx[data-fx="mariposas"]{
  --jcl-butterfly: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg fill='%23000'%3E%3Cellipse cx='12' cy='12.5' rx='1' ry='6'/%3E%3Cellipse cx='7' cy='8.6' rx='5' ry='4.6'/%3E%3Cellipse cx='17' cy='8.6' rx='5' ry='4.6'/%3E%3Cellipse cx='8.3' cy='16' rx='3.8' ry='4.4'/%3E%3Cellipse cx='15.7' cy='16' rx='3.8' ry='4.4'/%3E%3C/g%3E%3Cg stroke='%23000' stroke-width='.8' fill='none' stroke-linecap='round'%3E%3Cpath d='M11.6 6.6 9.2 3.2'/%3E%3Cpath d='M12.4 6.6 14.8 3.2'/%3E%3C/g%3E%3C/svg%3E");
}
@keyframes jclFlutter{
  0%,100% { transform: scaleX(1) rotate(-6deg); }
  50%     { transform: scaleX(.42) rotate(6deg); }
}

/* ============================================================
   12 · NIEVE DORADA — copos chicos con balanceo amplio
   ============================================================ */
.jcl-fx[data-fx="nieve_dorada"] i{ animation-name: jclFall; }
.jcl-fx[data-fx="nieve_dorada"] i::before{
  width:calc(var(--s, 1) * 5px);
  height:calc(var(--s, 1) * 5px);
  margin:calc(var(--s, 1) * -2.5px);
  border-radius:50%;
  background: var(--jcl-fx-color);
  box-shadow: 0 0 calc(var(--s, 1) * 6px) color-mix(in srgb, var(--jcl-fx-color) 60%, transparent);
  animation-name: jclSway;
}

/* ============================================================
   13 · BURBUJAS — suben con brillo especular y bamboleo
   ============================================================ */
.jcl-fx[data-fx="burbujas"] i{ animation-name: jclRise; }
.jcl-fx[data-fx="burbujas"] i::before{
  width:calc(var(--s, 1) * 14px);
  height:calc(var(--s, 1) * 14px);
  margin:calc(var(--s, 1) * -7px);
  border-radius:50%;
  border:1px solid color-mix(in srgb, var(--jcl-fx-color) 55%, transparent);
  background: radial-gradient(circle at 32% 28%,
    color-mix(in srgb, var(--jcl-fx-color) 85%, transparent) 0%,
    color-mix(in srgb, var(--jcl-fx-color) 12%, transparent) 38%,
    transparent 62%);
  animation-name: jclWobble;
}

/* ============================================================
   14 · CONFETI — rectángulos multicolor que voltean al caer
   ============================================================ */
.jcl-fx[data-fx="confeti"] i{ animation-name: jclFall; }
.jcl-fx[data-fx="confeti"] i::before{
  width:calc(var(--s, 1) * 7px);
  height:calc(var(--s, 1) * 12px);
  margin:calc(var(--s, 1) * -6px) 0 0 calc(var(--s, 1) * -3.5px);
  border-radius:1px;
  background: var(--jcl-fx-piece, var(--jcl-fx-color));
  animation-name: jclTumble;
  animation-timing-function: linear;
}

/* ============================================================
   15 · CORAZONES — suben latiendo
   ============================================================ */
.jcl-fx[data-fx="corazones"] i{ animation-name: jclRise; }
/* La silueta va como máscara SVG y no como `clip-path: path()`:
   path() usa píxeles absolutos, así que a 14px de lado recortaba un
   corazón dibujado para 24px y quedaba un borrón. La máscara escala
   con la caja. */
.jcl-fx[data-fx="corazones"] i::before{
  width:calc(var(--s, 1) * 16px);
  height:calc(var(--s, 1) * 16px);
  margin:calc(var(--s, 1) * -8px);
  background: var(--jcl-fx-color);
  -webkit-mask-image: var(--jcl-heart);
  mask-image: var(--jcl-heart);
  -webkit-mask-size: contain;
  mask-size: contain;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-position: center;
  mask-position: center;
  animation-name: jclHeartBeat;
}
.jcl-fx[data-fx="corazones"]{
  --jcl-heart: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23000' d='M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z'/%3E%3C/svg%3E");
}
@keyframes jclHeartBeat{
  0%,100% { transform: scale(1); }
  30%     { transform: scale(1.18); }
  55%     { transform: scale(.96); }
}


/* ============================================================
   INTENSIDAD — el editor puede bajar la carga visual sin
   cambiar de efecto. El motor recorta además la cantidad.
   ============================================================ */
.jcl-fx[data-intensity="suave"]{ opacity:.55; }
.jcl-fx[data-intensity="intenso"]{ opacity:1; }
.jcl-fx[data-intensity="normal"]{ opacity:.82; }


/* ============================================================
   CONFETI DE CONFIRMACIÓN — ráfaga única al enviar el RSVP.
   Reutiliza el mismo carril pero con animación no infinita.
   ============================================================ */
.jcl-fx-burst{
  position:absolute; inset:0;
  overflow:hidden; pointer-events:none; z-index:40;
  border-radius:inherit;
}
.jcl-fx-burst i{
  position:absolute;
  top:-6%;
  left:calc(var(--x, 50) * 1%);
  width:8px; height:13px;
  border-radius:1px;
  background: var(--jcl-fx-piece, #d4af37);
  animation: jclBurstFall calc(var(--d, 1.6) * 1s) cubic-bezier(.25,.6,.4,1) calc(var(--dl, 0) * 1s) forwards;
}
@keyframes jclBurstFall{
  0%   { opacity:1; transform: translate3d(0, 0, 0) rotate(0deg); }
  100% { opacity:0; transform: translate3d(calc(var(--dx, 0) * 1px), calc(var(--track, 420) * 1px), 0) rotate(calc(var(--r, 360) * 1deg)); }
}


/* ============================================================
   ACCESIBILIDAD Y AHORRO
   Si el visitante pidió menos movimiento, el efecto no se borra:
   queda como una capa estática y tenue, para no perder la
   decoración pero sí la animación.
   ============================================================ */
@media (prefers-reduced-motion: reduce){
  .jcl-fx i,
  .jcl-fx i::before,
  .jcl-fx i::after,
  .jcl-fx-burst i{
    animation: none !important;
  }
  .jcl-fx{ opacity:.35 !important; }
  .jcl-fx-burst{ display:none; }
}

/* Pausa cuando la tarjeta no está en pantalla: lo aplica el motor
   con un IntersectionObserver, así el móvil no anima de fondo. */
.jcl-fx.is-paused i,
.jcl-fx.is-paused i::before,
.jcl-fx.is-paused i::after{
  animation-play-state: paused !important;
}
