/* ============================================================================
   Alpha Branding Studio — Motion System
   assets/css/animations.css
   ----------------------------------------------------------------------------
   The theme's animation layer. Load after tokens.css, base.css, layout.css,
   utilities.css, buttons.css, forms.css.

   Contract:
   - Uses ONLY variables from tokens.css. No literal timing/easing/color values
     except the unitless keyframe geometry (translate distances, opacity).
   - Motion is mechanical and intentional — the camera shutter, never bouncy.
     Durations/easing come from tokens (--dur-*, --ease-*). No spring/overshoot.
   - Performance: animate ONLY opacity + transform (GPU-friendly, no layout/
     paint thrash). will-change is applied narrowly and released after reveal.
   - No JS. Scroll-reveal ships the hidden/visible states; an IntersectionObserver
     in theme JS just toggles .is-visible (or [data-inview]).
   - Everything degrades to instant final state under prefers-reduced-motion.
   ============================================================================ */


/* ==========================================================================
   1. KEYFRAMES
   Transform + opacity only. Small, confident distances (premium restraint).
   ========================================================================== */

@keyframes fade-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}

@keyframes fade-up {
  from { opacity: 0; transform: translate3d(0, var(--motion-shift, 1.5rem), 0); }
  to   { opacity: 1; transform: translate3d(0, 0, 0); }
}

@keyframes fade-down {
  from { opacity: 0; transform: translate3d(0, calc(var(--motion-shift, 1.5rem) * -1), 0); }
  to   { opacity: 1; transform: translate3d(0, 0, 0); }
}

@keyframes fade-left {
  /* content enters from the inline-end, settling toward start */
  from { opacity: 0; transform: translate3d(var(--motion-shift, 1.5rem), 0, 0); }
  to   { opacity: 1; transform: translate3d(0, 0, 0); }
}

@keyframes fade-right {
  from { opacity: 0; transform: translate3d(calc(var(--motion-shift, 1.5rem) * -1), 0, 0); }
  to   { opacity: 1; transform: translate3d(0, 0, 0); }
}

@keyframes scale-in {
  from { opacity: 0; transform: scale(0.96); }
  to   { opacity: 1; transform: scale(1); }
}

@keyframes scale-out {
  from { opacity: 1; transform: scale(1); }
  to   { opacity: 0; transform: scale(0.96); }
}

@keyframes slide-up {
  from { transform: translate3d(0, 100%, 0); }
  to   { transform: translate3d(0, 0, 0); }
}

@keyframes slide-down {
  from { transform: translate3d(0, -100%, 0); }
  to   { transform: translate3d(0, 0, 0); }
}

/* Very small rotation — a mechanical "settle", not a spin */
@keyframes rotate-subtle {
  from { transform: rotate(-1.5deg); }
  to   { transform: rotate(0deg); }
}

@keyframes spin {
  to { transform: rotate(360deg); }
}

/* Shimmer sweep for skeletons — moves a highlight band across the surface */
@keyframes shimmer {
  from { background-position: -150% 0; }
  to   { background-position: 250% 0; }
}

/* Skeleton pulse — opacity breathing when a shimmer sweep isn't used */
@keyframes skeleton-loading {
  0%,
  100% { opacity: 1; }
  50%  { opacity: 0.55; }
}


/* ==========================================================================
   2. ANIMATION UTILITY CLASSES
   One-shot entrances. `both` fill keeps the start state before + end after.
   ========================================================================== */

.animate-fade-in,
.animate-fade-up,
.animate-fade-down,
.animate-fade-left,
.animate-fade-right,
.animate-scale,
.animate-scale-in,
.animate-slide-up,
.animate-slide-down,
.animate-rotate {
  animation-duration: var(--dur-reveal);
  animation-timing-function: var(--ease-out);
  animation-fill-mode: both;
  animation-delay: var(--anim-delay, 0ms);
}

.animate-fade-in   { animation-name: fade-in; }
.animate-fade-up   { animation-name: fade-up; }
.animate-fade-down { animation-name: fade-down; }
.animate-fade-left { animation-name: fade-left; }
.animate-fade-right{ animation-name: fade-right; }
.animate-scale,
.animate-scale-in  { animation-name: scale-in; }
.animate-slide-up  { animation-name: slide-up; animation-timing-function: var(--ease-standard); }
.animate-slide-down{ animation-name: slide-down; animation-timing-function: var(--ease-standard); }
.animate-rotate    { animation-name: rotate-subtle; }

/* ---- Delay helpers (staggering) ---- */
.delay-0   { --anim-delay: 0ms; }
.delay-1   { --anim-delay: 60ms; }
.delay-2   { --anim-delay: 120ms; }
.delay-3   { --anim-delay: 180ms; }
.delay-4   { --anim-delay: 240ms; }
.delay-5   { --anim-delay: 300ms; }
.delay-6   { --anim-delay: 360ms; }

/* ---- Duration helpers (token-mapped) ---- */
.duration-fast   { animation-duration: var(--dur-fast); }
.duration-base   { animation-duration: var(--dur-base); }
.duration-slow   { animation-duration: var(--dur-slow); }
.duration-reveal { animation-duration: var(--dur-reveal); }

/* ---- Distance modifier (subtle vs standard travel) ---- */
.motion-sm { --motion-shift: 0.75rem; }
.motion-md { --motion-shift: 1.5rem; }
.motion-lg { --motion-shift: 2.5rem; }


/* ==========================================================================
   3. HOVER EFFECTS
   Structural hover primitives. Component files own their own hovers; these
   are reusable, opt-in helpers. Fine-pointer only (see §8 guard at file end
   applies reduced-motion; hover-capability guard here avoids sticky mobile).
   ========================================================================== */

@media (hover: hover) and (pointer: fine) {

  /* Card elevation lift */
  .hover-lift {
    transition: transform var(--dur-slow) var(--ease-standard),
                box-shadow var(--dur-slow) var(--ease-standard),
                border-color var(--dur-slow) var(--ease-standard);
  }
  .hover-lift:hover {
    transform: translate3d(0, -4px, 0);
    box-shadow: var(--shadow-md);
  }

  /* Subtle press-in for interactive tiles */
  .hover-press {
    transition: transform var(--dur-base) var(--ease-standard);
  }
  .hover-press:active {
    transform: scale(0.99);
  }

  /* Image zoom (element itself) */
  .hover-zoom {
    transition: transform var(--dur-slow) var(--ease-out);
  }
  .hover-zoom:hover {
    transform: scale(1.04);
  }

  /* Icon nudge on hover of a parent (e.g. link/button carrying .hover-icon) */
  .hover-icon .icon-shift {
    transition: transform var(--dur-base) var(--ease-out);
  }
  .hover-icon:hover .icon-shift {
    transform: translateX(var(--space-1));
  }

  /* Link underline reveal (draws inline-start -> inline-end) */
  .hover-underline {
    position: relative;
  }
  .hover-underline::after {
    content: "";
    position: absolute;
    inset-block-end: -2px;
    inset-inline-start: 0;
    inline-size: 100%;
    block-size: var(--border-thick);
    background-color: currentColor;
    transform: scaleX(0);
    transform-origin: inline-start;
    transition: transform var(--dur-base) var(--ease-out);
  }
  .hover-underline:hover::after,
  .hover-underline:focus-visible::after {
    transform: scaleX(1);
  }

  /* Accent color shift for icons/links */
  .hover-accent {
    transition: color var(--dur-fast) var(--ease-standard);
  }
  .hover-accent:hover {
    color: var(--color-flare);
  }
}


/* ==========================================================================
   4. IMAGE EFFECTS
   Media frames: zoom inside a clipped box, overlay + mask reveals. The frame
   clips; the <img> transforms. Pairs with layout.css .media-frame.
   ========================================================================== */

.img-hover {
  position: relative;
  overflow: hidden;
  isolation: isolate;
}

.img-hover > img,
.img-hover > picture > img {
  display: block;
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
  transition: transform var(--dur-slow) var(--ease-out),
              filter var(--dur-slow) var(--ease-out);
}

@media (hover: hover) and (pointer: fine) {
  .img-hover:hover > img,
  .img-hover:hover > picture > img,
  .img-hover:focus-within > img {
    transform: scale(1.04);
    filter: brightness(1.03);
  }
}

/* Overlay reveal — Ink scrim fades/rises in on hover (Portfolio Card) */
.img-overlay {
  position: relative;
  overflow: hidden;
  isolation: isolate;
}
.img-overlay::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  background: linear-gradient(
    to top,
    var(--color-ink) 0%,
    color-mix(in srgb, var(--color-ink) 40%, transparent) 45%,
    transparent 100%
  );
  opacity: 0;
  transition: opacity var(--dur-slow) var(--ease-standard);
  pointer-events: none;
}
.img-overlay > .img-overlay-content {
  position: absolute;
  inset-inline: 0;
  inset-block-end: 0;
  z-index: 2;
  padding: var(--space-5);
  color: var(--text-on-dark);
  transform: translate3d(0, var(--space-3), 0);
  opacity: 0;
  transition: transform var(--dur-slow) var(--ease-out),
              opacity var(--dur-slow) var(--ease-out);
}
@media (hover: hover) and (pointer: fine) {
  .img-overlay:hover::after,
  .img-overlay:focus-within::after { opacity: 1; }
  .img-overlay:hover > .img-overlay-content,
  .img-overlay:focus-within > .img-overlay-content {
    transform: translate3d(0, 0, 0);
    opacity: 1;
  }
}
/* Touch / no-hover: keep overlay content legible without interaction */
@media (hover: none) {
  .img-overlay::after { opacity: 1; }
  .img-overlay > .img-overlay-content { opacity: 1; transform: none; }
}

/* Mask reveal — a wipe that clips content open once (hero headline, images).
   Toggle .is-visible to play (JS/IntersectionObserver), or pair with reveal. */
.mask-reveal {
  clip-path: inset(0 100% 0 0);
  transition: clip-path var(--dur-reveal) var(--ease-out);
}
.mask-reveal.is-visible {
  clip-path: inset(0 0 0 0);
}


/* ==========================================================================
   5. SCROLL REVEAL PREPARATION
   Ships hidden + visible states only. Theme JS adds .is-visible (or sets
   [data-inview="true"]) when the element enters the viewport. No JS here.
   ========================================================================== */

/* Base hidden state — set on elements that should reveal on scroll */
.reveal {
  opacity: 0;
  transform: translate3d(0, var(--motion-shift, 1.5rem), 0);
  transition: opacity var(--dur-reveal) var(--ease-out),
              transform var(--dur-reveal) var(--ease-out);
  transition-delay: var(--anim-delay, 0ms);
  will-change: opacity, transform;
}

/* Directional variants (start state) */
.reveal-up    { transform: translate3d(0, var(--motion-shift, 1.5rem), 0); }
.reveal-down  { transform: translate3d(0, calc(var(--motion-shift, 1.5rem) * -1), 0); }
.reveal-left  { transform: translate3d(var(--motion-shift, 1.5rem), 0, 0); }
.reveal-right { transform: translate3d(calc(var(--motion-shift, 1.5rem) * -1), 0, 0); }
.reveal-scale { transform: scale(0.96); }
.reveal-fade  { transform: none; }

/* Visible state — both class- and data-attribute-driven for flexibility */
.reveal.is-visible,
.reveal[data-inview="true"] {
  opacity: 1;
  transform: none;
  will-change: auto;              /* release the hint once settled */
}

/* Stagger children of a revealed container (no per-item delay classes needed).
   Container gets .reveal-group; children animate in sequence when it's visible. */
.reveal-group > * {
  opacity: 0;
  transform: translate3d(0, var(--motion-shift, 1.5rem), 0);
  transition: opacity var(--dur-reveal) var(--ease-out),
              transform var(--dur-reveal) var(--ease-out);
}
.reveal-group.is-visible > * {
  opacity: 1;
  transform: none;
}
.reveal-group.is-visible > *:nth-child(1) { transition-delay: 0ms; }
.reveal-group.is-visible > *:nth-child(2) { transition-delay: 60ms; }
.reveal-group.is-visible > *:nth-child(3) { transition-delay: 120ms; }
.reveal-group.is-visible > *:nth-child(4) { transition-delay: 180ms; }
.reveal-group.is-visible > *:nth-child(5) { transition-delay: 240ms; }
.reveal-group.is-visible > *:nth-child(6) { transition-delay: 300ms; }
.reveal-group.is-visible > *:nth-child(7) { transition-delay: 360ms; }
.reveal-group.is-visible > *:nth-child(8) { transition-delay: 420ms; }


/* ==========================================================================
   6. LOADING STATES  (skeleton + shimmer)
   ========================================================================== */

/* Skeleton block — reserves layout, breathes via opacity (no layout shift) */
.skeleton {
  display: block;
  inline-size: 100%;
  background-color: var(--color-ash);
  border-radius: var(--radius-sm);
  animation: skeleton-loading 1.5s var(--ease-standard) infinite;
  /* Prevent skeletons from being read as content */
  pointer-events: none;
  user-select: none;
}

/* Shimmer variant — a highlight band sweeps across (premium loading cue) */
.skeleton-shimmer {
  position: relative;
  overflow: hidden;
  background-color: var(--color-ash);
  background-image: linear-gradient(
    100deg,
    transparent 20%,
    color-mix(in srgb, var(--color-bone) 55%, transparent) 40%,
    color-mix(in srgb, var(--color-bone) 55%, transparent) 55%,
    transparent 75%
  );
  background-size: 250% 100%;
  background-repeat: no-repeat;
  animation: shimmer 1.6s linear infinite;
}

/* Skeleton shape helpers */
.skeleton-text   { block-size: 1em; margin-block: 0.35em; border-radius: var(--radius-sm); }
.skeleton-title  { block-size: 1.5em; inline-size: 60%; border-radius: var(--radius-sm); }
.skeleton-line   { block-size: 0.9em; }
.skeleton-line-short { inline-size: 75%; }
.skeleton-avatar { inline-size: 44px; block-size: 44px; border-radius: var(--radius-pill); }
.skeleton-thumb  { aspect-ratio: 16 / 9; border-radius: var(--radius-lg); }
.skeleton-card   { block-size: 100%; border-radius: var(--radius-lg); }

/* Standalone spinner (non-button contexts: section/page loading) */
.spinner {
  display: inline-block;
  inline-size: var(--icon-lg);
  block-size: var(--icon-lg);
  border: var(--border-thick) solid var(--border-default);
  border-inline-end-color: var(--color-flare);
  border-radius: var(--radius-pill);
  animation: spin 0.7s linear infinite;
}
.spinner-sm { inline-size: var(--icon-md); block-size: var(--icon-md); }
.spinner-lg { inline-size: var(--space-8); block-size: var(--space-8); }


/* ==========================================================================
   7. MICRO-INTERACTIONS
   Small, reusable feedback details. Guarded to hover-capable devices.
   ========================================================================== */

@media (hover: hover) and (pointer: fine) {

  /* Arrow slide (e.g. "View all ->") — target an .arrow child */
  .mi-arrow .arrow {
    transition: transform var(--dur-base) var(--ease-out);
  }
  .mi-arrow:hover .arrow {
    transform: translateX(var(--space-2));
  }

  /* Icon rise */
  .mi-icon-rise .icon {
    transition: transform var(--dur-base) var(--ease-out);
  }
  .mi-icon-rise:hover .icon {
    transform: translateY(calc(var(--space-1) * -1));
  }

  /* Rotate affordance (accordion chevron / expandable) */
  .mi-rotate .chevron {
    transition: transform var(--dur-slow) var(--ease-standard);
  }
  .mi-rotate[aria-expanded="true"] .chevron,
  .mi-rotate.is-open .chevron {
    transform: rotate(180deg);
  }

  /* Subtle image scale on a card hover (image inside .card) */
  .mi-card-media img {
    transition: transform var(--dur-slow) var(--ease-out);
  }
  .mi-card-media:hover img {
    transform: scale(1.04);
  }
}

/* Chevron rotate must also work without hover (touch accordions) */
.mi-rotate .chevron {
  transition: transform var(--dur-slow) var(--ease-standard);
}
.mi-rotate[aria-expanded="true"] .chevron,
.mi-rotate.is-open .chevron {
  transform: rotate(180deg);
}


/* ==========================================================================
   8. PERFORMANCE NOTES (enforced in rules above)
   - Only opacity + transform are animated anywhere in this file.
   - will-change is set on .reveal (a known pre-animation state) and released
     to `auto` on .is-visible; it is NOT applied globally (which would waste
     compositor memory and hurt Lighthouse).
   - Hover transforms are gated behind (hover: hover) and (pointer: fine) to
     avoid sticky states and wasted work on touch devices.
   - No box-shadow/gradient is keyframe-animated (paint cost); shadows only
     transition on discrete hover, not in loops.
   ========================================================================== */

/* Opt-in compositor hint for elements known to animate imminently
   (e.g. a hero about to mask-reveal). Use sparingly. */
.will-animate { will-change: opacity, transform; }
.will-animate.is-visible { will-change: auto; }


/* ==========================================================================
   9. ACCESSIBILITY — prefers-reduced-motion
   Collapse all motion to instant final states. Nothing depends on animation
   to become visible (reveal elements resolve to opacity:1, transform:none).
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {

  /* Neutralize all animation utilities and one-shot entrances */
  .animate-fade-in,
  .animate-fade-up,
  .animate-fade-down,
  .animate-fade-left,
  .animate-fade-right,
  .animate-scale,
  .animate-scale-in,
  .animate-slide-up,
  .animate-slide-down,
  .animate-rotate {
    animation: none !important;
  }

  /* Reveal elements appear immediately, fully settled */
  .reveal,
  .reveal-up,
  .reveal-down,
  .reveal-left,
  .reveal-right,
  .reveal-scale,
  .reveal-fade,
  .reveal-group > * {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
    will-change: auto !important;
  }

  /* Mask reveal shows content without the wipe */
  .mask-reveal {
    clip-path: none !important;
    transition: none !important;
  }

  /* Hover/micro-interaction transforms disabled; color feedback retained */
  .hover-lift,
  .hover-press,
  .hover-zoom,
  .img-hover > img,
  .img-hover > picture > img,
  .mi-card-media img,
  .hover-icon .icon-shift,
  .mi-arrow .arrow,
  .mi-icon-rise .icon {
    transition: none !important;
    transform: none !important;
  }
  .hover-lift:hover,
  .hover-zoom:hover,
  .img-hover:hover > img,
  .mi-card-media:hover img {
    transform: none !important;
  }

  /* Overlay reveals resolve to visible (no slide) */
  .img-overlay::after { transition: none !important; }
  .img-overlay > .img-overlay-content {
    transition: none !important;
    transform: none !important;
  }

  /* Loading indicators: keep a slow, non-strobing cue rather than a fast spin */
  .skeleton,
  .skeleton-shimmer {
    animation-duration: 2.4s !important;
  }
  .spinner {
    animation-duration: 1.4s !important;
  }

  /* Underline reveal appears without the sweep */
  .hover-underline::after {
    transition: none !important;
  }

  /* Chevron still rotates (conveys state) but instantly */
  .mi-rotate .chevron {
    transition: none !important;
  }
}