/* ==========================================================================
   better-webui · motion primitives
   Framework-agnostic. Load after tokens.css.

   House rules (from affectUI's motion language):
     1. Everything that appears, appears from blur + 98% scale. Never from 0.
     2. Everything that changes size, glides — it does not jump.
     3. Motion carries state, not decoration. If nothing changed, nothing moves.
     4. prefers-reduced-motion collapses duration to 1ms via tokens, so these
        rules keep working without a second code path.
   ========================================================================== */

/* ---- entrance: blur-sharpen ------------------------------------------- */
@keyframes bw-sharpen-in {
  from {
    opacity: 0;
    filter: blur(var(--bw-blur-in));
    transform: scale(var(--bw-scale-in)) translateY(var(--bw-lift));
  }
  to {
    opacity: 1;
    filter: blur(0);
    transform: scale(1) translateY(0);
  }
}

.bw-enter {
  animation: bw-sharpen-in var(--bw-dur-slow) var(--bw-ease-out) both;
  animation-delay: calc(var(--bw-i, 0) * var(--bw-stagger));
}

/* ---- exit: mirror of entrance, softer --------------------------------- */
@keyframes bw-blur-out {
  from {
    opacity: 1;
    filter: blur(0);
    transform: scale(1);
  }
  to {
    opacity: 0;
    filter: blur(calc(var(--bw-blur-in) * 0.6));
    transform: scale(1.01);
  }
}

.bw-exit {
  animation: bw-blur-out var(--bw-dur-base) var(--bw-ease-in-out) both;
}

/* ---- press feedback ---------------------------------------------------- */
.bw-pressable {
  transition:
    transform var(--bw-dur-instant) var(--bw-ease-out),
    background-color var(--bw-dur-fast) var(--bw-ease-out),
    box-shadow var(--bw-dur-fast) var(--bw-ease-out);
}
.bw-pressable:hover {
  transform: translateY(calc(var(--bw-lift) * -1));
}
.bw-pressable:active {
  transform: scale(0.985);
  transition-duration: var(--bw-dur-instant);
}

/* ---- shimmer (skeleton) ------------------------------------------------ */
@keyframes bw-shimmer {
  from {
    background-position: -160% 0;
  }
  to {
    background-position: 260% 0;
  }
}

.bw-skeleton {
  border-radius: var(--bw-radius-sm);
  background-image: linear-gradient(
    100deg,
    transparent 20%,
    color-mix(in srgb, var(--bw-text) 8%, transparent) 45%,
    transparent 70%
  );
  background-size: 200% 100%;
  background-color: color-mix(in srgb, var(--bw-text) 5%, transparent);
  animation: bw-shimmer 1.9s var(--bw-ease-in-out) infinite;
}

/* ---- number ticker ----------------------------------------------------- */
/* Wrap each character in <span class="bw-tick-digit"> and swap textContent. */
.bw-tick-digit {
  display: inline-block;
  font-variant-numeric: tabular-nums;
  transition:
    transform var(--bw-dur-base) var(--bw-ease-out),
    opacity var(--bw-dur-base) var(--bw-ease-out),
    filter var(--bw-dur-base) var(--bw-ease-out);
}
.bw-tick-digit[data-state="out"] {
  transform: translateY(-0.55em);
  opacity: 0;
  filter: blur(4px);
}
.bw-tick-digit[data-state="out-down"] {
  transform: translateY(0.55em);
  opacity: 0;
  filter: blur(4px);
}

/* ---- text reveal ------------------------------------------------------- */
/* Each word wrapped in <span class="bw-reveal-word">, delay via --bw-i. */
@keyframes bw-word-rise {
  from {
    opacity: 0;
    filter: blur(6px);
    transform: translateY(0.4em);
  }
  to {
    opacity: 1;
    filter: blur(0);
    transform: translateY(0);
  }
}

.bw-reveal-word {
  display: inline-block;
  animation: bw-word-rise var(--bw-dur-slow) var(--bw-ease-out) both;
  animation-delay: calc(var(--bw-i, 0) * 28ms);
}

/* ---- height morph (the "box glides to its next size" behaviour) -------- */
/* Set --bw-h from JS scrollHeight, or rely on grid-template 0fr→1fr trick. */
.bw-morph {
  display: grid;
  grid-template-rows: 0fr;
  transition:
    grid-template-rows var(--bw-dur-swap) var(--bw-ease-out),
    opacity var(--bw-dur-swap) var(--bw-ease-out),
    filter var(--bw-dur-swap) var(--bw-ease-out);
  opacity: 0;
  filter: blur(6px);
}
.bw-morph > * {
  min-height: 0;
  overflow: hidden;
}
.bw-morph[data-open="true"] {
  grid-template-rows: 1fr;
  opacity: 1;
  filter: blur(0);
}

/* ---- tilt card --------------------------------------------------------- */
.bw-tilt {
  transform: perspective(900px) rotateX(var(--bw-rx, 0deg))
    rotateY(var(--bw-ry, 0deg)) scale(var(--bw-ts, 1));
  transition: transform var(--bw-dur-slow) var(--bw-ease-spring);
  will-change: transform;
}
.bw-tilt:active {
  --bw-ts: 0.995;
}

/* ---- marquee ----------------------------------------------------------- */
@keyframes bw-marquee {
  to {
    transform: translateX(-50%);
  }
}
.bw-marquee {
  display: flex;
  width: max-content;
  animation: bw-marquee var(--bw-marquee-dur, 32s) linear infinite;
}
.bw-marquee:hover {
  animation-play-state: paused;
}

/* ---- spinner: calm, eight-ray ----------------------------------------- */
@keyframes bw-orbit {
  to {
    transform: rotate(1turn);
  }
}
.bw-spinner {
  display: inline-block;
  width: 1em;
  height: 1em;
  border-radius: 50%;
  border: 1.5px solid color-mix(in srgb, currentColor 22%, transparent);
  border-top-color: currentColor;
  animation: bw-orbit 0.75s linear infinite;
}

/* ---- transition helper used by the theme toggle ----------------------- */
/* Applied briefly to <html> when View Transitions is unavailable.         */
html.bw-theme-transition,
html.bw-theme-transition *,
html.bw-theme-transition *::before,
html.bw-theme-transition *::after {
  transition:
    background-color var(--bw-dur-theme) var(--bw-ease-soft),
    border-color var(--bw-dur-theme) var(--bw-ease-soft),
    color var(--bw-dur-theme) var(--bw-ease-soft),
    fill var(--bw-dur-theme) var(--bw-ease-soft),
    stroke var(--bw-dur-theme) var(--bw-ease-soft) !important;
  transition-delay: 0ms !important;
}

/* View Transitions: kill the default root crossfade so only our clip runs. */
::view-transition-old(root),
::view-transition-new(root) {
  animation: none;
  mix-blend-mode: normal;
}
