/* ═══ LA CAPA VIAJA CON LA HOJA (2026-07-20) ══════════════════════════════════
   Esta hoja declara su @layer ACÁ ADENTRO. Antes la capa la ponía quien la
   importaba —`@import url(...) layer(lib)`, repetido en 4 emisores— así que
   la hoja caía en su capa SOLO si el importador acertaba, y cargarla por
   <link> la dejaba SIN CAPA: en CSS lo no-capado le gana a todo lo capado.
   Con la capa acá la hoja es inmune a CÓMO se la cargue, y el <head> puede
   usar <link> (paralelo, y un fallo se ve en Network) en vez de @import
   (serial, render-blocking, y si falla se pierde EN SILENCIO — el defecto que
   dejaba el shell sin `container-type` y tiraba el layout a flujo plano).
   ═════════════════════════════════════════════════════════════════════════ */
@layer lib {
/* ═══════════════════════════════════════════════════════════════════════════════════════════════
 * mx-effects.css — CONTRATO de EFECTOS del kit (mejoras-front F4).
 *
 * El vocabulario fx-*/elev-*/motion-* CANÓNICO, SHIPPEADO con el kit (viaja en _content/MxLite.UI.Cx/) y cargado
 * en @layer lib → disponible en TODO preview/app, con o SIN skin. Antes vivía fragmentado en 3 lugares
 * (0-core/MxLite.Core/css/effects.css que NO viaja, aurora/skin.css que solo existe con ese skin, y un fx-sheen
 * duplicado) → una clase como fx-lift/fx-glass DESAPARECÍA en silencio fuera del skin aurora. Ahora es contrato.
 *
 * REGLA: las utilidades leen tokens con FALLBACK brand-aware (color-mix sobre --mx-color-primary) → se re-tiñen
 * con el brand activo. Los SKINS pueden PISAR esos tokens (--mx-glow-primary, --mx-glass-bg, --mx-elevation-*),
 * NO redefinir las clases. Los @keyframes (fx-aurora-spin, fx-comal-rise, cx-marquee) viven en Az/components.css.
 * ═══════════════════════════════════════════════════════════════════════════════════════════════ */

/* SSOT (auditoría 2026-07-18 · Fase B1): esta hoja YA NO define tokens de efecto — solo CLASES.
   Los VALORES de --mx-glow-*/--mx-glass-*/--mx-elevation-*/--mx-grain-opacity viven en UNA casa:
   color/theme-recipes.css (los emite ColorEngine.EmitRichTokens por tema, brand-aware). Acá había una
   copia con VALORES DIVERGENTES (glow 45%↔35%, blur 10px↔8px) que ganaba o perdía según la cascada —
   diseñar contra un dado. ContratosSsotTests fija que esta hoja no vuelva a declarar esos tokens. */

/* ── ELEVACIÓN (opt-in por clase; mapea a las sombras reales del tema) ─────────────────────────── */
.elev-0{box-shadow:var(--mx-elevation-0)}
.elev-1{box-shadow:var(--mx-elevation-1)}
.elev-2{box-shadow:var(--mx-elevation-2)}
.elev-3{box-shadow:var(--mx-elevation-3)}
.elev-4{box-shadow:var(--mx-elevation-4)}
.elev-5{box-shadow:var(--mx-elevation-5)}

/* ── GLOW (halo del rol) ───────────────────────────────────────────────────────────────────────── */
.fx-glow{box-shadow:var(--mx-glow-primary)}
.fx-glow-success{box-shadow:var(--mx-glow-success)}
.fx-glow-danger{box-shadow:var(--mx-glow-danger)}
.fx-glow-info{box-shadow:var(--mx-glow-info)}

/* ── GRADIENTES / MESH / AURORA (superficies) ──────────────────────────────────────────────────── */
.fx-gradient{background:var(--mx-gradient-primary)}
.fx-gradient-2{background:var(--mx-gradient-secondary, var(--mx-gradient-primary))}
.fx-gradient-text{background:var(--mx-gradient-primary);-webkit-background-clip:text;background-clip:text;color:transparent}
.fx-mesh{background:var(--mx-gradient-mesh, var(--mx-gradient-primary))}
.fx-aurora{background:var(--mx-gradient-aurora, var(--mx-gradient-primary))}
.fx-aurora-live{animation:fx-aurora-spin 34s linear infinite}

/* ── GLASS (vidrio esmerilado, brand-aware) ────────────────────────────────────────────────────── */
.fx-glass{
  background:var(--mx-glass-bg);
  backdrop-filter:var(--mx-glass-blur);-webkit-backdrop-filter:var(--mx-glass-blur);
  border:var(--mx-glass-border);
  box-shadow:var(--mx-glass-shadow);
}

/* ── SHEEN (velo superior sutil) + GRAIN (textura fractal) ─────────────────────────────────────── */
.fx-sheen{position:relative;background-image:var(--mx-color-surface-grad,linear-gradient(180deg,rgba(255,255,255,.12),transparent 45%))}
.fx-grain{position:relative}
.fx-grain::after{content:'';position:absolute;inset:0;pointer-events:none;opacity:var(--mx-grain-opacity);mix-blend-mode:overlay;
  background:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E")}

/* ── MOVIMIENTO (hover-lift premium + pop al click) ────────────────────────────────────────────── */
.fx-lift{transition:transform var(--mx-dur-2,.18s) var(--mx-ease-emphasized,cubic-bezier(.2,.8,.2,1)),
  box-shadow var(--mx-dur-2,.18s) var(--mx-ease-emphasized,ease)}
.fx-lift:hover{transform:translateY(-6px);box-shadow:var(--mx-elevation-5),var(--mx-glow-primary)}
.motion-pop{transition:transform var(--mx-dur-1,120ms) ease}
.motion-pop:active{transform:scale(.97)}

/* PARALLAX al scroll (scroll-driven, sin JS) — @keyframes en Az/components.css */
@supports (animation-timeline: scroll()){
  .fx-parallax-slow{animation:fx-comal-rise linear both;animation-timeline:scroll(root block);animation-range:0 130vh;will-change:transform}
  .fx-parallax-mid{animation:fx-comal-rise-mid linear both;animation-timeline:scroll(root block);animation-range:0 130vh;will-change:transform}
}

/* accesibilidad: sin movimiento si el usuario lo pidió */
@media (prefers-reduced-motion: reduce){
  .fx-aurora-live,.fx-parallax-slow,.fx-parallax-mid{animation:none}
  .fx-lift,.motion-pop{transition:none}
}

/* ── FONDOS VIVOS · animación de fondo declarativa (Semillero), gateada por Temperamento ────────────
 * La INTENCIÓN viaja como class="mxa mxa-{efecto}" + custom-props --mxa-* en el SVG inline de .mx-bg-decor
 * (los emite PatronRender). La FÍSICA vive acá, UNA vez para todos los sitios. El dial Temperamento escala:
 * duración × --mx-anim-dur-mult, amplitud × --mx-anim-amp. Solo transform/opacity (compositor). Default
 * QUIETO: sin gating (Dials.Gating) no hay vida — un tema que no gatea queda estático, como hoy. */
:root{ --mx-anim-play:paused; --mx-anim-dur-mult:1; --mx-anim-amp:0 }
.mx-bg-decor .mxa{
  transform-box:fill-box; transform-origin:center;
  animation-play-state:var(--mx-anim-play);
  animation-duration:calc(var(--mxa-dur,20s) * var(--mx-anim-dur-mult));
  animation-delay:var(--mxa-delay,0s);
  animation-timing-function:ease-in-out;
  animation-iteration-count:infinite;
}
.mx-bg-decor .mxa-deriva  { animation-name:mxa-deriva }
.mx-bg-decor .mxa-pulso   { animation-name:mxa-pulso }
.mx-bg-decor .mxa-giro    { animation-name:mxa-giro; animation-timing-function:linear }
.mx-bg-decor .mxa-ola     { animation-name:mxa-ola }
.mx-bg-decor .mxa-parpadeo{ animation-name:mxa-parpadeo }
.mx-bg-decor .mxa-ascenso { animation-name:mxa-ascenso; animation-timing-function:linear }

/* amplitud efectiva = --mxa-amp (del motivo, por instancia) × --mx-anim-amp (del dial). En g0-2 el dial
 * pone --mx-anim-amp:0 → el transform computa a 0 (doble candado con play-state:paused). */
@keyframes mxa-deriva{
  0%,100%{ transform:translate(0,0) }
  33%{ transform:translate(calc(6% * var(--mxa-amp,1) * var(--mx-anim-amp)), calc(-5% * var(--mxa-amp,1) * var(--mx-anim-amp))) }
  66%{ transform:translate(calc(-5% * var(--mxa-amp,1) * var(--mx-anim-amp)), calc(4% * var(--mxa-amp,1) * var(--mx-anim-amp))) }
}
@keyframes mxa-pulso{
  0%,100%{ transform:scale(1) }
  50%{ transform:scale(calc(1 + .06 * var(--mxa-amp,1) * var(--mx-anim-amp))) }
}
@keyframes mxa-giro{ from{ transform:rotate(0) } to{ transform:rotate(360deg) } }
@keyframes mxa-ola{
  0%,100%{ transform:scaleY(1) }
  50%{ transform:scaleY(calc(1 + .08 * var(--mxa-amp,1) * var(--mx-anim-amp))) }
}
/* parpadeo: piso de opacidad (subtil siempre) — nunca sube a 1 y frecuencia ≤ 0.5 Hz por la dur del motivo */
@keyframes mxa-parpadeo{
  0%,100%{ opacity:.06 }
  50%{ opacity:calc(.06 + .05 * var(--mxa-amp,1) * var(--mx-anim-amp)) }
}
/* ascenso (F2 fondos-vivos · la «Deriva dulce» del estudio): la capa nace BAJO el lienzo (cy>100, clipeada
 * por el viewBox) y sube en línea recta hasta salir por arriba, fundiéndose en las puntas del ciclo — el
 * loop no tiene salto perceptible POR CONSTRUCCIÓN. La opacidad va HORNEADA acá (precedente mxa-parpadeo,
 * fix del juez de viabilidad): animar opacity PISA el atributo del elemento, así que el crucero absoluto
 * (.12, dentro de la banda 0.05–0.14 del estudio) × --mx-anim-amp vive en el keyframe. Doble candado en
 * grados formales: play-state paused + amp 0 → opacity 0 aunque un retraso negativo lo pause en pleno
 * vuelo (sin amp acá, un delay -40s dejaría un fantasma estático visible). reduced-motion → animation:none
 * → el elemento queda en su posición yaml (fuera del viewBox) = invisible. Distancia -130 unidades de
 * lienzo × amplitud de instancia: toda burbuja cruza el lienzo entero. */
@keyframes mxa-ascenso{
  0%{ transform:translateY(0); opacity:0 }
  10%{ opacity:calc(.12 * var(--mx-anim-amp)) }
  90%{ opacity:calc(.12 * var(--mx-anim-amp)) }
  100%{ transform:translateY(calc(-130px * var(--mxa-amp,1))); opacity:0 }
}

/* LA LEY: reducir movimiento apaga todo el sistema de fondos vivos con una regla */
@media (prefers-reduced-motion: reduce){
  .mx-bg-decor .mxa{ animation:none !important }
}
}
