/* motion.css — motion design system.

   Six-step duration scale + four-curve easing system. Every transition and
   keyframe in components.css and layout.css consumes these tokens; literal
   ms values are forbidden outside this file.

   Reduced-motion: durations collapse to ~0; layout, structure, and final
   states stay intact. Per-rule transition: none overrides live alongside
   their respective selectors (see existing blocks in components.css and
   layout.css for the splide carousel and portfolio card swap).
*/

:root {
  --motion-duration-instant: 0ms;
  --motion-duration-xs: 120ms;  /* hover tint, focus ring fade, button press */
  --motion-duration-sm: 180ms;  /* button press release, tag toggle, lang pill */
  --motion-duration-md: 260ms;  /* drawer, scrim, scrolled header, card hover */
  --motion-duration-lg: 420ms;  /* reveal-in on scroll */
  --motion-duration-xl: 640ms;  /* hero settle, page entry */

  --motion-ease-standard:   cubic-bezier(0.2, 0.0, 0, 1);
  --motion-ease-emphasized: cubic-bezier(0.2, 0.0, 0, 1.1);
  --motion-ease-decelerate: cubic-bezier(0.0, 0.0, 0.2, 1);
  --motion-ease-accelerate: cubic-bezier(0.4, 0.0, 1.0, 1);
}

@media (prefers-reduced-motion: reduce) {
  :root {
    --motion-duration-xs: 0.01ms;
    --motion-duration-sm: 0.01ms;
    --motion-duration-md: 0.01ms;
    --motion-duration-lg: 0.01ms;
    --motion-duration-xl: 0.01ms;
  }
}

/* Reveal-in utility (consumed by motion.js IntersectionObserver) ----------- */
.reveal {
  opacity: 0;
  transform: translate3d(0, 0.75rem, 0);
  transition: opacity var(--motion-duration-lg) var(--motion-ease-decelerate),
              transform var(--motion-duration-lg) var(--motion-ease-decelerate);
  will-change: opacity, transform;
}
.reveal.is-in {
  opacity: 1;
  transform: none;
}

.reveal--stagger > * {
  opacity: 0;
  transform: translate3d(0, 0.5rem, 0);
  transition: opacity var(--motion-duration-md) var(--motion-ease-decelerate),
              transform var(--motion-duration-md) var(--motion-ease-decelerate);
}
.reveal--stagger.is-in > *:nth-child(1) { transition-delay: 0ms; }
.reveal--stagger.is-in > *:nth-child(2) { transition-delay: 60ms; }
.reveal--stagger.is-in > *:nth-child(3) { transition-delay: 120ms; }
.reveal--stagger.is-in > *:nth-child(n+4) { transition-delay: 180ms; }
.reveal--stagger.is-in > * {
  opacity: 1;
  transform: none;
}

@media (prefers-reduced-motion: reduce) {
  .reveal,
  .reveal--stagger > * {
    opacity: 1;
    transform: none;
  }
}

/* Section-shell reveal — subtle scale + fade for full <section> blocks below
   the hero. Fires once via the same IntersectionObserver that drives .reveal
   and .reveal--stagger. Children keep their own .reveal / .reveal--stagger
   for stagger; the two layers compose cleanly. */
.reveal-section {
  opacity: 0;
  transform: scale(0.985);
  transform-origin: center top;
  transition: opacity var(--motion-duration-xl) var(--motion-ease-decelerate),
              transform var(--motion-duration-xl) var(--motion-ease-decelerate);
  will-change: opacity, transform;
}
.reveal-section.is-in {
  opacity: 1;
  transform: none;
}

@media (prefers-reduced-motion: reduce) {
  .reveal-section {
    opacity: 1;
    transform: none;
  }
}

/* Form status + per-field errors ------------------------------------------ */
.sp-form__status {
  opacity: 0;
  transform: translate3d(0, 0.25rem, 0);
  transition: opacity var(--motion-duration-sm) var(--motion-ease-decelerate),
              transform var(--motion-duration-sm) var(--motion-ease-decelerate);
}
.sp-form__status[data-status="submitting"],
.sp-form__status[data-status="success"],
.sp-form__status[data-status="error"] {
  opacity: 1;
  transform: none;
}

.sp-form-error {
  opacity: 0;
  transform: translate3d(0, 0.125rem, 0);
  transition: opacity var(--motion-duration-xs) var(--motion-ease-decelerate),
              transform var(--motion-duration-xs) var(--motion-ease-decelerate);
}
.sp-form-error.is-visible {
  opacity: 1;
  transform: none;
}

/* Pill-thumb pattern ------------------------------------------------------
   Tag containers (portfolio filters, contact segments, lang toggle) get a
   single absolutely-positioned thumb injected by motion.js. Active children
   have a transparent background so the thumb's amber fill shows through; the
   text color toggle stays where it lives in components.css/layout.css. */
.sp-portfolio-filters,
.sp-contact__segments,
.sp-lang-toggle {
  position: relative;
}
.sp-tag-thumb {
  position: absolute;
  top: 0;
  left: 0;
  width: var(--thumb-w, 0);
  height: var(--thumb-h, 100%);
  transform: translate3d(var(--thumb-x, 0), var(--thumb-y, 0), 0);
  border-radius: var(--radius-full, 999px);
  background: var(--color-amber);
  pointer-events: none;
  opacity: 0;
  z-index: 0;
  transition: transform var(--motion-duration-sm) var(--motion-ease-standard),
              width var(--motion-duration-sm) var(--motion-ease-standard),
              height var(--motion-duration-sm) var(--motion-ease-standard),
              opacity var(--motion-duration-xs) var(--motion-ease-standard);
}
.has-active-thumb > .sp-tag-thumb {
  opacity: 1;
}
/* Tags / lang buttons must stack above the thumb so the text renders. */
.sp-portfolio-filters .sp-tag,
.sp-contact__segments .sp-tag,
.sp-lang-toggle__btn {
  position: relative;
  z-index: 1;
}
/* Active state — once the thumb is in play, the tag's own background and
   border step aside so the thumb provides the entire visual. Selectors mirror
   the existing .is-active specificity (the #proyectos one in particular). */
#proyectos .sp-portfolio-filters.has-active-thumb .sp-tag.is-active,
.sp-portfolio-filters.has-active-thumb .sp-tag.is-active,
.sp-contact__segments.has-active-thumb .sp-tag.is-active {
  background: transparent;
  border-color: transparent;
}
.sp-lang-toggle.has-active-thumb .sp-lang-toggle__btn.is-active {
  background: transparent;
}

/* Lang-toggle text crossfade ---------------------------------------------- */
/* Scoped to <main> to avoid flashing nav links/CTA during the swap. lang.js
   adds .is-lang-changing to <body> right before the string swap, then removes
   it on the next animation frame after the swap completes. */
body.is-lang-changing main [data-i18n],
body.is-lang-changing main [data-i18n-aria] {
  opacity: 0;
  transition: opacity var(--motion-duration-xs) var(--motion-ease-standard);
}
