/*! flux-animation v2.2.0 — a micro motion library for chat UIs
 *  ~4.2 KB gzipped · zero JS · transform/opacity first (GPU-composited, smooth on Windows + macOS + iOS)
 *
 *  USAGE
 *    Entrances/exits:  <div class="fx-up">          (add class to play once)
 *    Speed:            fx-faster | fx-fast | fx-slow | fx-slower
 *    Delay:            fx-d1 … fx-d5  (.1s … .5s)
 *    Easing:           fx-spring | fx-linear
 *    Repeat:           fx-loop
 *    Lists:            <ul class="fx-stagger"> (children rise in sequence)
 *    Rotating status:  <div class="fx-cycle-3"><span>…</span>×3</div>   (auto-rotates, zero JS)
 *                      or swap pairs: fx-thought ⇄ fx-thought-out, fx-roll-up ⇄ fx-roll-up-out
 *    In-flight:        fx-trace · fx-comet · fx-fill · fx-orbit · fx-bloom · fx-dots-wave · fx-dots-spinner · …
 *    Width morphing:   fx-width (transition) · fx-expand-w / fx-collapse-w (keyframed)
 *    Tune globally:    :root { --fx-dur:.4s; --fx-dist:12px; --fx-stagger:50ms; --fx-cycle:6s }
 *
 *  Hover effects run on pointer devices; on touch they respond to :active (press).
 *  Mirrored directions share one keyframe parameterized by a private var (--_x/--_y/--_s/--_r).
 */

:root {
  interpolate-size: allow-keywords; /* lets .fx-width transition to/from width:auto in Chromium */
  --fx-dur: .45s;
  --fx-dist: 14px;
  --fx-stagger: 55ms;
  --fx-ease: cubic-bezier(.22, 1, .36, 1);        /* smooth decel  */
  --fx-spring: cubic-bezier(.34, 1.56, .64, 1);   /* gentle overshoot */
}

[class^="fx-"], [class*=" fx-"] {
  animation-duration: var(--fx-dur);
  animation-timing-function: var(--fx-ease);
  animation-fill-mode: both;
  backface-visibility: hidden;
}

/* ───────────────────────── ENTRANCES (21) ───────────────────────── */
.fx-fade       { animation-name: fxFade }
.fx-up         { animation-name: fxY; --_y: var(--fx-dist) }
.fx-down       { animation-name: fxY; --_y: calc(var(--fx-dist) * -1) }
.fx-left       { animation-name: fxX; --_x: calc(var(--fx-dist) * -1) }   /* enters from the left  */
.fx-right      { animation-name: fxX; --_x: var(--fx-dist) }              /* enters from the right */
.fx-zoom       { animation-name: fxZoom }
.fx-zoom-up    { animation-name: fxZoomUp }
.fx-pop        { animation-name: fxPop; animation-timing-function: var(--fx-spring) }
.fx-bounce-in  { animation-name: fxBounceIn; animation-duration: .6s }
.fx-blur       { animation-name: fxBlur }
.fx-rotate     { animation-name: fxRotate }
.fx-flip-x     { animation-name: fxFlipX }
.fx-flip-y     { animation-name: fxFlipY }
.fx-wipe       { animation-name: fxWipe }
.fx-grow-x     { animation-name: fxGrowX; transform-origin: left center }
.fx-sheet-up   { animation-name: fxSheet; --_s: 110% }    /* bottom sheet / drawer */
.fx-sheet-down { animation-name: fxSheet; --_s: -110% }   /* top drawer / banner   */
.fx-zoom-down  { animation-name: fxZoomDown }                                   /* drop + scale         */
.fx-swing      { animation-name: fxSwing; transform-origin: top center }        /* hinges down from top */
.fx-roll-in    { animation-name: fxRollIn }                                     /* rolls in from left   */
.fx-reveal     { animation-name: fxReveal }                                     /* unmasks top → bottom */

@keyframes fxFade     { from { opacity: 0 } }
@keyframes fxY        { from { opacity: 0; transform: translate3d(0, var(--_y), 0) } }
@keyframes fxX        { from { opacity: 0; transform: translate3d(var(--_x), 0, 0) } }
@keyframes fxZoom     { from { opacity: 0; transform: scale(.9) } }
@keyframes fxZoomUp   { from { opacity: 0; transform: translate3d(0, var(--fx-dist), 0) scale(.97) } }
@keyframes fxPop      { from { opacity: 0; transform: scale(.85) } }
@keyframes fxBounceIn { 0% { opacity: 0; transform: scale(.8) } 60% { opacity: 1; transform: scale(1.05) } 80% { transform: scale(.98) } 100% { transform: scale(1) } }
@keyframes fxBlur     { from { opacity: 0; filter: blur(6px) } to { opacity: 1; filter: blur(0) } }
@keyframes fxRotate   { from { opacity: 0; transform: rotate(-8deg) scale(.94) } }
@keyframes fxFlipX    { from { opacity: 0; transform: perspective(640px) rotateX(-22deg) } to { opacity: 1; transform: perspective(640px) rotateX(0) } }
@keyframes fxFlipY    { from { opacity: 0; transform: perspective(640px) rotateY(14deg) }  to { opacity: 1; transform: perspective(640px) rotateY(0) } }
@keyframes fxWipe     { from { opacity: 0; clip-path: inset(0 100% 0 0) } to { opacity: 1; clip-path: inset(0 0 0 0) } }
@keyframes fxGrowX    { from { opacity: 0; transform: scale(.4, 1) } }
@keyframes fxSheet    { from { transform: translate3d(0, var(--_s), 0) } }
@keyframes fxZoomDown { from { opacity: 0; transform: translate3d(0, calc(var(--fx-dist) * -1), 0) scale(.97) } }
@keyframes fxSwing    { from { opacity: 0; transform: perspective(640px) rotateX(-22deg) } to { opacity: 1; transform: perspective(640px) rotateX(0) } }
@keyframes fxRollIn   { from { opacity: 0; transform: translate3d(calc(var(--fx-dist) * -1), 0, 0) rotate(-8deg) } }
@keyframes fxReveal   { from { opacity: 0; clip-path: inset(0 0 100% 0) } to { opacity: 1; clip-path: inset(0 0 0 0) } }

/* ───────────────────────── EXITS (12) ───────────────────────── */
.fx-fade-out  { animation-name: fxFadeOut }
.fx-up-out    { animation-name: fxYOut; --_y: calc(var(--fx-dist) * -1) }  /* leaves upward    */
.fx-down-out  { animation-name: fxYOut; --_y: var(--fx-dist) }             /* leaves downward  */
.fx-left-out  { animation-name: fxXOut; --_x: calc(var(--fx-dist) * -2) }  /* leaves to the left  */
.fx-slide-out { animation-name: fxXOut; --_x: calc(var(--fx-dist) * 2) }   /* leaves to the right */
.fx-zoom-out  { animation-name: fxZoomOut }
.fx-pop-out   { animation-name: fxPopOut }
.fx-blur-out  { animation-name: fxBlurOut }
.fx-sheet-out { animation-name: fxSheetOut }                               /* sheet slides back down */
.fx-rotate-out{ animation-name: fxRotateOut }                              /* tilts away as it fades */
.fx-flip-x-out{ animation-name: fxFlipXOut }                               /* tips back in 3D        */
.fx-drop-out  { animation-name: fxDropOut }                                /* falls away downward    */

@keyframes fxFadeOut  { to { opacity: 0 } }
@keyframes fxYOut     { to { opacity: 0; transform: translate3d(0, var(--_y), 0) } }
@keyframes fxXOut     { to { opacity: 0; transform: translate3d(var(--_x), 0, 0) } }
@keyframes fxZoomOut  { to { opacity: 0; transform: scale(.95) } }
@keyframes fxPopOut   { to { opacity: 0; transform: scale(.82) } }
@keyframes fxBlurOut  { to { opacity: 0; filter: blur(6px) } }
@keyframes fxSheetOut { to { transform: translate3d(0, 110%, 0) } }
@keyframes fxRotateOut{ to { opacity: 0; transform: rotate(6deg) scale(.94) } }
@keyframes fxFlipXOut { to { opacity: 0; transform: perspective(640px) rotateX(14deg) } }
@keyframes fxDropOut  { to { opacity: 0; transform: translate3d(0, calc(var(--fx-dist) * 2), 0) rotate(6deg) } }

/* ─────────────── ATTENTION (15) · one-shot; add .fx-loop to repeat ─────────────── */
.fx-pulse     { animation-name: fxPulse; animation-duration: .9s }
.fx-bounce    { animation-name: fxBounce; animation-duration: .8s }
.fx-shake     { animation-name: fxShake; animation-duration: .5s; animation-timing-function: linear }
.fx-wiggle    { animation-name: fxWiggle; animation-duration: .7s }
.fx-jello     { animation-name: fxJello; animation-duration: .9s }
.fx-rubber    { animation-name: fxRubber; animation-duration: .9s }
.fx-nudge     { animation-name: fxNudge; animation-duration: 1s }
.fx-flash     { animation-name: fxFlash; animation-duration: .9s }
.fx-heartbeat { animation-name: fxHeartbeat; animation-duration: 1.1s }
.fx-glow      { animation-name: fxGlow; animation-duration: 1.4s }
.fx-float     { animation-name: fxFloat; animation-duration: 3s; animation-iteration-count: infinite; animation-timing-function: ease-in-out }
.fx-tada      { animation-name: fxTada; animation-duration: 1s }
.fx-buzz      { animation-name: fxBuzz; animation-duration: .4s; animation-timing-function: linear }
.fx-bell      { animation-name: fxBell; animation-duration: 1s; transform-origin: top center }
.fx-ring:focus-visible { animation: fxRing .55s var(--fx-ease) }

@keyframes fxPulse     { 50% { transform: scale(1.05) } }
@keyframes fxBounce    { 0%,100% { transform: translate3d(0,0,0) } 30% { transform: translate3d(0,-25%,0) } 55% { transform: translate3d(0,0,0) } 70% { transform: translate3d(0,-10%,0) } 85% { transform: translate3d(0,0,0) } }
@keyframes fxShake     { 0%,100% { transform: translate3d(0,0,0) } 20%,60% { transform: translate3d(-5px,0,0) } 40%,80% { transform: translate3d(5px,0,0) } }
@keyframes fxWiggle    { 0%,100% { transform: rotate(0) } 20% { transform: rotate(-4deg) } 40% { transform: rotate(4deg) } 60% { transform: rotate(-2deg) } 80% { transform: rotate(2deg) } }
@keyframes fxJello     { 0%,100% { transform: skewX(0) } 30% { transform: skewX(-8deg) } 45% { transform: skewX(5deg) } 60% { transform: skewX(-3deg) } 75% { transform: skewX(2deg) } }
@keyframes fxRubber    { 0%,100% { transform: scale(1) } 30% { transform: scale(1.15,.85) } 45% { transform: scale(.9,1.1) } 60% { transform: scale(1.05,.95) } 75% { transform: scale(.98,1.02) } }
@keyframes fxNudge     { 0%,100% { transform: translate3d(0,0,0) } 25%,65% { transform: translate3d(6px,0,0) } 45%,85% { transform: translate3d(0,0,0) } }
@keyframes fxFlash     { 25%,75% { opacity: .3 } 50%,100% { opacity: 1 } }
@keyframes fxHeartbeat { 0%,40%,100% { transform: scale(1) } 14%,28% { transform: scale(1.12) } }
@keyframes fxGlow      { 0%,100% { box-shadow: 0 0 0 0 transparent } 50% { box-shadow: 0 0 18px 2px var(--fx-glow, rgba(79,70,229,.45)) } }
@keyframes fxFloat     { 50% { transform: translate3d(0,-6px,0) } }
@keyframes fxRing      { 0% { box-shadow: 0 0 0 0 var(--fx-ring, rgba(79,70,229,.5)) } 100% { box-shadow: 0 0 0 9px transparent } }
@keyframes fxTada      { 0%,100% { transform: scale(1) rotate(0) } 10%,20% { transform: scale(.92) rotate(-3deg) } 30%,50%,70%,90% { transform: scale(1.1) rotate(3deg) } 40%,60%,80% { transform: scale(1.1) rotate(-3deg) } }
@keyframes fxBuzz      { 0%,100% { transform: translate3d(0,0,0) } 20%,60% { transform: translate3d(-2px,0,0) } 40%,80% { transform: translate3d(2px,0,0) } }
@keyframes fxBell      { 0%,100% { transform: rotate(0) } 15% { transform: rotate(11deg) } 30% { transform: rotate(-9deg) } 45% { transform: rotate(6deg) } 60% { transform: rotate(-4deg) } 75% { transform: rotate(2deg) } }

/* ─────────────── BUTTONS & HOVER (11) · transitions, not keyframes ───────────────
   Hover styles apply on devices that hover; on touch, the same effect fires on press. */
.fx-btn  { transition: transform .15s var(--fx-ease), box-shadow .15s var(--fx-ease) }
.fx-btn:active  { transform: scale(.96) }
.fx-push { transition: transform .12s var(--fx-ease) }
.fx-push:active { transform: translateY(1.5px) scale(.985) }
.fx-lift, .fx-grow { transition: transform .2s var(--fx-ease), box-shadow .2s var(--fx-ease) }
.fx-tilt { transition: transform .25s var(--fx-ease) }
.fx-brighten { transition: filter .2s var(--fx-ease) }
.fx-glow-h   { transition: box-shadow .2s var(--fx-ease) }
.fx-rotate-h { transition: transform .2s var(--fx-ease) }
.fx-skew-h   { transition: transform .2s var(--fx-ease) }
.fx-shine { position: relative; overflow: hidden }
.fx-shine::after { content: ""; position: absolute; inset: 0; transform: translateX(-110%) skewX(-18deg);
  background: linear-gradient(90deg, transparent, rgba(255,255,255,.35), transparent); transition: transform .55s var(--fx-ease) }
.fx-underline { position: relative }
.fx-underline::after { content: ""; position: absolute; left: 0; right: 0; bottom: -2px; height: 2px;
  background: currentColor; transform: scaleX(0); transform-origin: left center; transition: transform .25s var(--fx-ease) }

@media (hover: hover) {
  .fx-lift:hover  { transform: translateY(-2px); box-shadow: 0 6px 16px rgba(0,0,0,.12) }
  .fx-grow:hover  { transform: scale(1.04) }
  .fx-tilt:hover  { transform: perspective(640px) rotateX(3deg) rotateY(-3deg) translateY(-1px) }
  .fx-brighten:hover { filter: brightness(1.12) }
  .fx-glow-h:hover   { box-shadow: 0 0 18px 2px var(--fx-glow, rgba(79,70,229,.45)) }
  .fx-rotate-h:hover { transform: rotate(3deg) scale(1.03) }
  .fx-skew-h:hover   { transform: skewX(-7deg) }
  .fx-shine:hover::after { transform: translateX(110%) skewX(-18deg) }
  .fx-underline:hover::after { transform: scaleX(1) }
}
@media (hover: none) {  /* touch fallback: press to trigger */
  .fx-lift:active  { transform: translateY(-2px); box-shadow: 0 6px 16px rgba(0,0,0,.12) }
  .fx-grow:active  { transform: scale(1.04) }
  .fx-tilt:active  { transform: perspective(640px) rotateX(3deg) rotateY(-3deg) translateY(-1px) }
  .fx-brighten:active { filter: brightness(1.12) }
  .fx-glow-h:active   { box-shadow: 0 0 18px 2px var(--fx-glow, rgba(79,70,229,.45)) }
  .fx-rotate-h:active { transform: rotate(3deg) scale(1.03) }
  .fx-skew-h:active   { transform: skewX(-7deg) }
  .fx-shine:active::after { transform: translateX(110%) skewX(-18deg) }
  .fx-underline:active::after { transform: scaleX(1) }
}

/* ─────────────────────── LISTS / STAGGER (6) ─────────────────────── */
.fx-stagger > * { animation: fxZoomUp var(--fx-dur) var(--fx-ease) both }
.fx-stagger-fade > * { animation-name: fxFade }
.fx-stagger-left > * { animation-name: fxX; --_x: calc(var(--fx-dist) * -1) }
.fx-stagger-right > * { animation-name: fxX; --_x: var(--fx-dist) }
.fx-stagger-pop  > * { animation-name: fxPop; animation-timing-function: var(--fx-spring) }
.fx-stagger-blur > * { animation-name: fxBlur }
.fx-stagger > :nth-child(1)  { animation-delay: calc(var(--fx-stagger) * 1) }
.fx-stagger > :nth-child(2)  { animation-delay: calc(var(--fx-stagger) * 2) }
.fx-stagger > :nth-child(3)  { animation-delay: calc(var(--fx-stagger) * 3) }
.fx-stagger > :nth-child(4)  { animation-delay: calc(var(--fx-stagger) * 4) }
.fx-stagger > :nth-child(5)  { animation-delay: calc(var(--fx-stagger) * 5) }
.fx-stagger > :nth-child(6)  { animation-delay: calc(var(--fx-stagger) * 6) }
.fx-stagger > :nth-child(7)  { animation-delay: calc(var(--fx-stagger) * 7) }
.fx-stagger > :nth-child(8)  { animation-delay: calc(var(--fx-stagger) * 8) }
.fx-stagger > :nth-child(9)  { animation-delay: calc(var(--fx-stagger) * 9) }
.fx-stagger > :nth-child(10) { animation-delay: calc(var(--fx-stagger) * 10) }
.fx-stagger > :nth-child(11) { animation-delay: calc(var(--fx-stagger) * 11) }
.fx-stagger > :nth-child(12) { animation-delay: calc(var(--fx-stagger) * 12) }
.fx-stagger > :nth-child(n+13) { animation-delay: calc(var(--fx-stagger) * 13) }

/* ─────────────── ROTATING UPDATES (11) · for cycling status / "thinking" text ───────────────
   Swap pairs (JS swaps content): add the -out class to the old node, the in class to the new one.
   fx-cycle-N (zero JS): stack 2–4 children in the container; each takes a turn automatically.
   Roll classes travel the element's own height — wrap old+new in an .fx-clip parent. */
.fx-thought       { animation-name: fxThought }                /* blur-rise in — thought surfaces      */
.fx-thought-out   { animation-name: fxThoughtOut }             /* blur-rise away — thought dissolves   */
.fx-roll-up       { animation-name: fxRoll;    --_r: 105% }    /* ticker: new line rolls up from below */
.fx-roll-up-out   { animation-name: fxRollOut; --_r: -105% }
.fx-roll-down     { animation-name: fxRoll;    --_r: -105% }   /* ticker: rolls down from above        */
.fx-roll-down-out { animation-name: fxRollOut; --_r: 105% }
.fx-clip          { overflow: hidden }                         /* clipping wrapper for roll tickers    */

.fx-cycle-2, .fx-cycle-3, .fx-cycle-4, .fx-cycle-5 { display: grid; --fx-cycle: 6s }
.fx-cycle-2 > *, .fx-cycle-3 > *, .fx-cycle-4 > *, .fx-cycle-5 > * {
  grid-area: 1 / 1; opacity: 0;
  animation: fxCycle2 var(--fx-cycle) var(--fx-ease) infinite both }
.fx-cycle-3 > * { animation-name: fxCycle3 }
.fx-cycle-4 > * { animation-name: fxCycle4 }
.fx-cycle-5 > * { animation-name: fxCycle5 }
.fx-cycle-2 > :nth-child(2) { animation-delay: calc(var(--fx-cycle) / 2) }
.fx-cycle-3 > :nth-child(2) { animation-delay: calc(var(--fx-cycle) / 3) }
.fx-cycle-3 > :nth-child(3) { animation-delay: calc(var(--fx-cycle) / 3 * 2) }
.fx-cycle-4 > :nth-child(2) { animation-delay: calc(var(--fx-cycle) / 4) }
.fx-cycle-4 > :nth-child(3) { animation-delay: calc(var(--fx-cycle) / 2) }
.fx-cycle-4 > :nth-child(4) { animation-delay: calc(var(--fx-cycle) / 4 * 3) }
.fx-cycle-5 > :nth-child(2) { animation-delay: calc(var(--fx-cycle) / 5) }
.fx-cycle-5 > :nth-child(3) { animation-delay: calc(var(--fx-cycle) / 5 * 2) }
.fx-cycle-5 > :nth-child(4) { animation-delay: calc(var(--fx-cycle) / 5 * 3) }
.fx-cycle-5 > :nth-child(5) { animation-delay: calc(var(--fx-cycle) / 5 * 4) }

@keyframes fxThought    { from { opacity: 0; transform: translate3d(0, 7px, 0);  filter: blur(4px) } to { opacity: 1; transform: translate3d(0,0,0); filter: blur(0) } }
@keyframes fxThoughtOut { to   { opacity: 0; transform: translate3d(0, -7px, 0); filter: blur(4px) } }
@keyframes fxRoll       { from { transform: translate3d(0, var(--_r), 0) } }
@keyframes fxRollOut    { to   { transform: translate3d(0, var(--_r), 0) } }
@keyframes fxCycle2 { 0% { opacity: 0; transform: translate3d(0,8px,0) } 7%,43%  { opacity: 1; transform: translate3d(0,0,0) } 50%,100%   { opacity: 0; transform: translate3d(0,-8px,0) } }
@keyframes fxCycle3 { 0% { opacity: 0; transform: translate3d(0,8px,0) } 5%,28%  { opacity: 1; transform: translate3d(0,0,0) } 33.3%,100% { opacity: 0; transform: translate3d(0,-8px,0) } }
@keyframes fxCycle4 { 0% { opacity: 0; transform: translate3d(0,8px,0) } 4%,21%  { opacity: 1; transform: translate3d(0,0,0) } 25%,100%   { opacity: 0; transform: translate3d(0,-8px,0) } }
@keyframes fxCycle5 { 0% { opacity: 0; transform: translate3d(0,8px,0) } 3%,17%  { opacity: 1; transform: translate3d(0,0,0) } 20%,100%   { opacity: 0; transform: translate3d(0,-8px,0) } }

/* ─────────────── IN-FLIGHT (13) · something is being worked on ─────────────── */
.fx-trace { position: relative; overflow: hidden }           /* a line draws across, releases, redraws */
.fx-trace::after { content: ""; position: absolute; inset: 0; border-radius: inherit;
  background: currentColor; transform: translateX(-101%);
  animation: fxTrace 1.7s cubic-bezier(.65,0,.35,1) infinite }
.fx-comet { position: relative; overflow: hidden }           /* a streak with a fading tail crosses a track */
.fx-comet::after { content: ""; position: absolute; top: 0; bottom: 0; width: 36%; border-radius: inherit;
  background: linear-gradient(90deg, transparent, currentColor); transform: translateX(-110%);
  animation: fxComet 1.5s cubic-bezier(.45,0,.2,1) infinite }
.fx-fill { position: relative; overflow: hidden }            /* tint sweeps the element, holds, dissolves */
.fx-fill::before { content: ""; position: absolute; inset: 0;
  background: var(--fx-fill, rgba(79,70,229,.14)); transform: translateX(-101%);
  animation: fxFill 1.9s var(--fx-ease) infinite }

.fx-scan { position: relative; overflow: hidden }            /* a beam sweeps back and forth across a track */
.fx-scan::after { content: ""; position: absolute; top: 0; bottom: 0; width: 28%; border-radius: inherit;
  background: linear-gradient(90deg, transparent, currentColor, transparent); transform: translateX(-120%);
  animation: fxScan 1.5s var(--fx-ease) infinite alternate }
.fx-ripple { animation: fxRipple 1.5s var(--fx-ease) infinite }  /* concentric halo — a node is processing */

/* calm loaders · slow, low-amplitude, ease in/out — meant to soothe, not rush */
.fx-orbit   { position: relative; animation: fxSpin 1.8s linear infinite }   /* a dot circles its center */
.fx-orbit::after { content: ""; position: absolute; top: 0; left: 50%; width: 6px; height: 6px;
  margin-left: -3px; border-radius: 50%; background: currentColor }
.fx-bloom   { animation: fxBloom 2.6s ease-in-out infinite }     /* a halo breathes around the element  */

/* dot loaders · a row (or ring) of dots animating in sequence — drop plain <i> children inside */
[class^="fx-dots-"], [class*=" fx-dots-"] { display: inline-flex; gap: 5px; align-items: center; --fx-dot: 7px }
[class^="fx-dots-"] i, [class*=" fx-dots-"] i { flex: none; width: var(--fx-dot); height: var(--fx-dot);
  border-radius: 50%; background: currentColor }
.fx-dots-wave    i { animation: fxDotsWave 1.3s ease-in-out infinite }    /* a gentle travelling sine        */
.fx-dots-fade    i { animation: fxDotsFade 1.4s ease-in-out infinite }    /* brightness chases along the row */
.fx-dots-scale   i { animation: fxDotsScale 1.4s ease-in-out infinite }   /* size swells along the row       */
.fx-dots-flow    i { animation: fxDotsFlow 1.4s ease-in-out infinite }    /* a soft drifting stream          */
.fx-dots-elastic i { animation: fxDotsElastic 1.2s var(--fx-spring) infinite }  /* a springy hop            */
.fx-dots-wave i:nth-child(2), .fx-dots-fade i:nth-child(2), .fx-dots-scale i:nth-child(2),
.fx-dots-flow i:nth-child(2), .fx-dots-elastic i:nth-child(2) { animation-delay: .14s }
.fx-dots-wave i:nth-child(3), .fx-dots-fade i:nth-child(3), .fx-dots-scale i:nth-child(3),
.fx-dots-flow i:nth-child(3), .fx-dots-elastic i:nth-child(3) { animation-delay: .28s }
.fx-dots-spinner { position: relative; display: inline-block; width: 30px; height: 30px }   /* ring of 3 dots */
.fx-dots-spinner i { position: absolute; top: 50%; left: 50%; margin: calc(var(--fx-dot) / -2);
  animation: fxDotsRing 1.2s linear infinite }
.fx-dots-spinner i:nth-child(1) { transform: rotate(0deg)   translateY(-12px); animation-delay: -1.2s }
.fx-dots-spinner i:nth-child(2) { transform: rotate(120deg) translateY(-12px); animation-delay: -.8s }
.fx-dots-spinner i:nth-child(3) { transform: rotate(240deg) translateY(-12px); animation-delay: -.4s }

@keyframes fxTrace { 0% { transform: translateX(-101%) } 45%, 55% { transform: translateX(0) } 100% { transform: translateX(101%) } }
@keyframes fxComet { to { transform: translateX(390%) } }
@keyframes fxFill  { 0% { transform: translateX(-101%); opacity: 1 } 60% { transform: translateX(0); opacity: 1 } 100% { transform: translateX(0); opacity: 0 } }
@keyframes fxScan  { from { transform: translateX(-120%) } to { transform: translateX(380%) } }
@keyframes fxRipple{ 0% { box-shadow: 0 0 0 0 var(--fx-ring, rgba(79,70,229,.45)) } 100% { box-shadow: 0 0 0 13px transparent } }
@keyframes fxBloom    { 0%,100% { box-shadow: 0 0 0 0 transparent } 50% { box-shadow: 0 0 15px 1px var(--fx-glow, rgba(79,70,229,.4)) } }
@keyframes fxDotsWave   { 0%,60%,100% { transform: translateY(0) } 30% { transform: translateY(-8px) } }
@keyframes fxDotsFade   { 0%,100% { opacity: .25 } 30% { opacity: 1 } }
@keyframes fxDotsScale  { 0%,100% { transform: scale(.55); opacity: .5 } 35% { transform: scale(1); opacity: 1 } }
@keyframes fxDotsFlow   { 0% { transform: translateX(-5px); opacity: 0 } 35%,65% { transform: translateX(0); opacity: 1 } 100% { transform: translateX(5px); opacity: 0 } }
@keyframes fxDotsElastic{ 0%,50%,100% { transform: translateY(0) } 25% { transform: translateY(-9px) } }
@keyframes fxDotsRing   { 0% { opacity: 1 } 100% { opacity: .15 } }

/* ─────────────── WIDTH & SIZE (10) ───────────────
   fx-width is a transition: toggle a class / inline width and it tweens — with
   interpolate-size enabled it even tweens to width:auto in Chromium.
   fx-expand-w / fx-collapse-w animate max-width (layout work — fine for occasional
   UI moments, avoid in tight loops). The scale variants are pure GPU. */
.fx-width { transition: width .35s var(--fx-ease), max-width .35s var(--fx-ease),
            min-width .35s var(--fx-ease), flex-basis .35s var(--fx-ease) }
.fx-expand-w   { animation-name: fxExpandW;   overflow: hidden; white-space: nowrap }
.fx-collapse-w { animation-name: fxCollapseW; overflow: hidden; white-space: nowrap }
.fx-shrink-x   { animation-name: fxShrinkX; transform-origin: left center }   /* GPU exit, pairs with fx-grow-x */
.fx-grow-y     { animation-name: fxGrowY;   transform-origin: top center }    /* GPU expand (accordion feel)    */
.fx-shrink-y   { animation-name: fxShrinkY; transform-origin: top center }
.fx-expand-h   { animation-name: fxExpandH;   overflow: hidden }              /* max-height accordion open      */
.fx-collapse-h { animation-name: fxCollapseH; overflow: hidden }              /* max-height accordion close     */
.fx-grow-x-to   { animation-name: fxScaleXTo; transform-origin: left center; --fx-x: 1.3 }  /* widens then HOLDS at --fx-x (1.3 = 130%) — content stays */
.fx-shrink-x-to { animation-name: fxScaleXTo; transform-origin: left center; --fx-x: .6 }   /* narrows then HOLDS at --fx-x (.6 = 60%)  — content stays */

@keyframes fxExpandW   { from { max-width: 0; opacity: 0 } to { max-width: var(--fx-w, 320px); opacity: 1 } }
@keyframes fxCollapseW { from { max-width: var(--fx-w, 320px) } to { max-width: 0; opacity: 0 } }
@keyframes fxShrinkX   { to   { opacity: 0; transform: scale(.4, 1) } }
@keyframes fxGrowY     { from { opacity: 0; transform: scale(1, .6) } }
@keyframes fxShrinkY   { to   { opacity: 0; transform: scale(1, .6) } }
@keyframes fxExpandH   { from { max-height: 0; opacity: 0 } to { max-height: var(--fx-h, 300px); opacity: 1 } }
@keyframes fxCollapseH { from { max-height: var(--fx-h, 300px) } to { max-height: 0; opacity: 0 } }
@keyframes fxScaleXTo  { from { transform: scaleX(1) } to { transform: scaleX(var(--fx-x, 1)) } }

/* ─────────────────── CHAT & LOADING PRIMITIVES (11) ─────────────────── */
.fx-msg      { animation-name: fxZoomUp }                                   /* assistant bubble  */
.fx-msg-user { animation-name: fxMsgUser }                                  /* user bubble       */
.fx-stream   { animation: fxFade .3s var(--fx-ease) both }                  /* streamed chunk    */
.fx-cursor::after { content: ""; display: inline-block; width: 2px; height: 1em;
  margin-left: 2px; vertical-align: -.12em; background: currentColor;
  animation: fxBlink 1.1s steps(2, jump-none) infinite }
.fx-dots { display: inline-flex; gap: 4px; align-items: center }
.fx-dots i { width: 6px; height: 6px; border-radius: 50%; background: currentColor;
  animation: fxDot 1.2s ease-in-out infinite }
.fx-dots i:nth-child(2) { animation-delay: .15s }
.fx-dots i:nth-child(3) { animation-delay: .3s }
.fx-shimmer { position: relative; overflow: hidden }
.fx-shimmer::after { content: ""; position: absolute; inset: 0; transform: translateX(-100%);
  background: linear-gradient(90deg, transparent, rgba(255,255,255,.45), transparent);
  animation: fxShimmer 1.6s linear infinite }
.fx-pulse-soft { animation: fxPulseSoft 1.5s ease-in-out infinite }          /* skeleton breathe  */
.fx-spin { animation: fxSpin .8s linear infinite }
.fx-progress { position: relative; overflow: hidden }                       /* indeterminate bar */
.fx-progress::after { content: ""; position: absolute; top: 0; bottom: 0; left: 0; width: 40%;
  border-radius: inherit; background: currentColor; transform: translateX(-110%);
  animation: fxProgress 1.2s ease-in-out infinite }
.fx-bars { display: inline-flex; gap: 3px; align-items: center; height: 1em }  /* equalizer / voice input */
.fx-bars i { width: 3px; height: 100%; border-radius: 2px; background: currentColor;
  transform: scaleY(.4); transform-origin: center; animation: fxBars 1s ease-in-out infinite }
.fx-bars i:nth-child(2) { animation-delay: .15s }
.fx-bars i:nth-child(3) { animation-delay: .3s }
.fx-bars i:nth-child(4) { animation-delay: .45s }
.fx-ping { position: relative }                                              /* live presence dot */
.fx-ping::after { content: ""; position: absolute; inset: 0; border-radius: inherit;
  box-shadow: 0 0 0 0 var(--fx-ring, rgba(79,70,229,.5)); animation: fxRipple 1.5s var(--fx-ease) infinite }

@keyframes fxMsgUser  { from { opacity: 0; transform: translate3d(12px, 6px, 0) scale(.97) } }
@keyframes fxBlink    { 50% { opacity: 0 } }
@keyframes fxDot      { 0%,60%,100% { transform: translate3d(0,0,0); opacity: .35 } 30% { transform: translate3d(0,-4px,0); opacity: 1 } }
@keyframes fxShimmer  { to { transform: translateX(100%) } }
@keyframes fxPulseSoft{ 50% { opacity: .45 } }
@keyframes fxSpin     { to { transform: rotate(1turn) } }
@keyframes fxProgress { to { transform: translateX(350%) } }
@keyframes fxBars     { 0%,100% { transform: scaleY(.4) } 50% { transform: scaleY(1) } }

/* ───────────────────────── MODIFIERS (19) ───────────────────────── */
.fx-faster { --fx-dur: .2s }
.fx-fast   { --fx-dur: .3s }
.fx-slow   { --fx-dur: .7s }
.fx-slower { --fx-dur: 1.1s }
.fx-d1 { animation-delay: .1s } .fx-d2 { animation-delay: .2s } .fx-d3 { animation-delay: .3s }
.fx-d4 { animation-delay: .4s } .fx-d5 { animation-delay: .5s }
.fx-d6 { animation-delay: .6s } .fx-d7 { animation-delay: .7s }
.fx-spring { animation-timing-function: var(--fx-spring) }
.fx-linear { animation-timing-function: linear }
.fx-loop { animation-iteration-count: infinite }
.fx-loop-2 { animation-iteration-count: 2 }
.fx-loop-3 { animation-iteration-count: 3 }
.fx-reverse { animation-direction: reverse }
.fx-paused { animation-play-state: paused }

/* ─────────────────────── ACCESSIBILITY ─────────────────────── */
@media (prefers-reduced-motion: reduce) {
  [class^="fx-"], [class*=" fx-"], .fx-stagger > *,
  .fx-shine::after, .fx-underline::after, .fx-cursor::after,
  .fx-dots i, .fx-bars i, .fx-shimmer::after, .fx-progress::after,
  .fx-trace::after, .fx-comet::after, .fx-fill::before,
  .fx-scan::after, .fx-ping::after, .fx-orbit::after,
  [class^="fx-dots-"] i, [class*=" fx-dots-"] i {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition: none !important;
  }
  /* rotators: stop cycling, show the first item */
  .fx-cycle-2 > *, .fx-cycle-3 > *, .fx-cycle-4 > *, .fx-cycle-5 > * { animation: none !important; opacity: 0 }
  .fx-cycle-2 > :first-child, .fx-cycle-3 > :first-child, .fx-cycle-4 > :first-child, .fx-cycle-5 > :first-child { opacity: 1 }
}
