/*
  Motion-Layer. Presets sind rein deklarativ (data-anim="..."), siehe
  docs/design/inventar.md für die Herleitung. Kein Preset animiert etwas ausser
  transform/opacity (CLAUDE.md: Textlängen aus dem CMS ändern sich, Layout muss
  stabil bleiben -- keine height/margin/top-Animationen).

  Baseline: ohne JS (kein html.js) oder mit prefers-reduced-motion ist alles
  sofort im Endzustand sichtbar. Ein JS-Fehler kann also nie eine leere Seite
  erzeugen, weil die "unsichtbar starten"-Regeln ausschliesslich unter
  html.js:not(.reduced-motion):not(.motion-off) gelten.

  .motion-off (siehe edit.js) wird genau wie .reduced-motion behandelt: im
  Bearbeitungsmodus sind alle Scroll-Animationen standardmässig aus (CLAUDE.md,
  Etappe 4), damit Inhalte klickbar bleiben. .motion-off ist absichtlich von
  .editing entkoppelt: edit.js setzt .editing für Bearbeitungs-Affordanzen
  (contenteditable, Block-Toolbar) und errechnet .motion-off separat aus
  ".editing AND NOT Animationen-abspielen-Toggle" -- so kann man Animationen
  im Bearbeitungsmodus vorschauen, ohne die Editier-Affordanzen zu verlieren.
*/

@media (prefers-reduced-motion: reduce) {
  html.js {
    scroll-behavior: auto;
  }
}

html.js:not(.reduced-motion):not(.motion-off) [data-anim]:not([data-anim="none"]) {
  --anim-dur: var(--dur-base);
  --anim-ease: var(--ease-out-expo);
}

/* ---- fade ---- */
html.js:not(.reduced-motion):not(.motion-off) [data-anim="fade"] {
  opacity: 0;
  transition: opacity var(--anim-dur) var(--anim-ease);
}

/* ---- reveal-up ---- */
html.js:not(.reduced-motion):not(.motion-off) [data-anim="reveal-up"] {
  opacity: 0;
  /* rotate(var(--item-rotate, 0deg)) is a no-op (0deg) for every element
     except the freely rotated product_detail images/bubbles, which set that
     custom property inline (see blocks.css) -- keeps their tilt intact
     through the reveal instead of a plain translateY overwriting it. */
  transform: translateY(var(--anim-distance)) rotate(var(--item-rotate, 0deg));
  transition: opacity var(--anim-dur) var(--anim-ease), transform var(--anim-dur) var(--anim-ease);
}

/* ---- scale-in ---- */
html.js:not(.reduced-motion):not(.motion-off) [data-anim="scale-in"] {
  opacity: 0;
  transform: scale(0.92) rotate(var(--item-rotate, 0deg));
  transition: opacity var(--anim-dur) var(--anim-ease), transform var(--anim-dur) var(--anim-ease);
}

/* ---- reveal-left / reveal-right: gleiches Prinzip wie reveal-up, nur auf
   der X- statt Y-Achse -- fuer Bilder/Karten, die spuerbar von der Seite
   statt von unten kommen sollen. ---- */
html.js:not(.reduced-motion):not(.motion-off) [data-anim="reveal-left"] {
  opacity: 0;
  transform: translateX(calc(-1 * var(--anim-distance))) rotate(var(--item-rotate, 0deg));
  transition: opacity var(--anim-dur) var(--anim-ease), transform var(--anim-dur) var(--anim-ease);
}

html.js:not(.reduced-motion):not(.motion-off) [data-anim="reveal-right"] {
  opacity: 0;
  transform: translateX(var(--anim-distance)) rotate(var(--item-rotate, 0deg));
  transition: opacity var(--anim-dur) var(--anim-ease), transform var(--anim-dur) var(--anim-ease);
}

/* ---- reveal-down: reveal-up gespiegelt, kommt von oben statt von unten. ---- */
html.js:not(.reduced-motion):not(.motion-off) [data-anim="reveal-down"] {
  opacity: 0;
  transform: translateY(calc(-1 * var(--anim-distance))) rotate(var(--item-rotate, 0deg));
  transition: opacity var(--anim-dur) var(--anim-ease), transform var(--anim-dur) var(--anim-ease);
}

/* ---- zoom-in: staerkerer Skalierungs-Ursprung + kurze Unschaerfe fuer
   einen raeumlicheren "kommt aus der Tiefe"-Eindruck als das dezente
   scale-in. ---- */
html.js:not(.reduced-motion):not(.motion-off) [data-anim="zoom-in"] {
  opacity: 0;
  transform: scale(1.7) rotate(var(--item-rotate, 0deg));
  filter: blur(10px);
  transition: opacity var(--anim-dur) var(--anim-ease), transform var(--anim-dur) var(--anim-ease), filter var(--anim-dur) var(--anim-ease);
}

/* ---- reveal-mask: covering pseudo-element slides away via transform only ---- */
html.js:not(.reduced-motion):not(.motion-off) [data-anim="reveal-mask"] {
  position: relative;
  overflow: hidden;
}

html.js:not(.reduced-motion):not(.motion-off) [data-anim="reveal-mask"]::after {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--color-bg-light);
  transform-origin: right center;
  transform: scaleX(1);
  transition: transform var(--dur-slow) var(--anim-ease);
  pointer-events: none;
}

html.js:not(.reduced-motion):not(.motion-off) [data-anim="reveal-mask"] img,
html.js:not(.reduced-motion):not(.motion-off) [data-anim="reveal-mask"] video {
  transform: scale(1.08);
  transition: transform var(--dur-slow) var(--anim-ease);
}

/* ---- stagger-children: children of the container animate like reveal-up,
   with an incremental delay per position ---- */
html.js:not(.reduced-motion):not(.motion-off) [data-anim="stagger-children"] > * {
  opacity: 0;
  transform: translateY(var(--anim-distance));
  transition: opacity var(--anim-dur) var(--anim-ease), transform var(--anim-dur) var(--anim-ease);
}

html.js:not(.reduced-motion):not(.motion-off) [data-anim="stagger-children"].is-visible > * {
  opacity: 1;
  transform: translateY(0);
}

/* Stagger delay per position -- plain nth-child, no JS math needed. */
html.js:not(.reduced-motion):not(.motion-off) [data-anim="stagger-children"] > *:nth-child(1) { transition-delay: calc(var(--dur-fast) * 0); }
html.js:not(.reduced-motion):not(.motion-off) [data-anim="stagger-children"] > *:nth-child(2) { transition-delay: calc(var(--dur-fast) * 1); }
html.js:not(.reduced-motion):not(.motion-off) [data-anim="stagger-children"] > *:nth-child(3) { transition-delay: calc(var(--dur-fast) * 2); }
html.js:not(.reduced-motion):not(.motion-off) [data-anim="stagger-children"] > *:nth-child(4) { transition-delay: calc(var(--dur-fast) * 3); }
html.js:not(.reduced-motion):not(.motion-off) [data-anim="stagger-children"] > *:nth-child(5) { transition-delay: calc(var(--dur-fast) * 4); }
html.js:not(.reduced-motion):not(.motion-off) [data-anim="stagger-children"] > *:nth-child(6) { transition-delay: calc(var(--dur-fast) * 5); }
html.js:not(.reduced-motion):not(.motion-off) [data-anim="stagger-children"] > *:nth-child(7) { transition-delay: calc(var(--dur-fast) * 6); }
html.js:not(.reduced-motion):not(.motion-off) [data-anim="stagger-children"] > *:nth-child(8) { transition-delay: calc(var(--dur-fast) * 7); }

/* ---- anim-delay / anim-intensity modifiers (set as inline custom properties by block templates) ---- */
html.js:not(.reduced-motion):not(.motion-off) [data-anim]:not([data-anim="stagger-children"]) {
  transition-delay: var(--anim-delay, 0ms);
}

html.js:not(.reduced-motion):not(.motion-off) [data-anim-intensity="subtil"] {
  --anim-distance: 12px;
}

html.js:not(.reduced-motion):not(.motion-off) [data-anim-intensity="stark"] {
  --anim-distance: 64px;
}

/* ---- visible state, reached either by native scroll-driven animation
   (below) or by the IntersectionObserver fallback in motion.js adding
   .is-visible ----
   scrub-sequence excluded like parallax: it's GSAP/ScrollTrigger-owned
   (product-sandwich.js), never a one-shot reveal. Setting even an identity
   `transform: none` here would still count as a transform on the pinned
   ancestor, which turns GSAP's `position: fixed` pin into one contained by
   that ancestor instead of the viewport (CSS: any transform establishes a
   containing block for fixed descendants) -- breaks the pin silently. */
html.js:not(.reduced-motion):not(.motion-off) [data-anim]:not([data-anim="scrub-sequence"]).is-visible {
  opacity: 1;
  transform: none;
}

/* More specific than the rule above on purpose: elements with a rotation
   custom property (product_detail images) need to keep it once revealed,
   not snap back to `none`. Scoped to the concrete class instead of changing
   the generic rule above -- an identity rotate(0deg), unlike `none`,
   establishes a containing block for any position:fixed descendant, which
   would silently break something like the GSAP pin on product-sandwich.js
   if it ever ended up nested in a generically-reveal-animated ancestor. */
html.js:not(.reduced-motion):not(.motion-off) [data-anim].block-product-detail__image.is-visible {
  transform: rotate(var(--item-rotate, 0deg));
}

html.js:not(.reduced-motion):not(.motion-off) [data-anim="reveal-mask"].is-visible::after {
  transform: scaleX(0);
}

html.js:not(.reduced-motion):not(.motion-off) [data-anim="reveal-mask"].is-visible img,
html.js:not(.reduced-motion):not(.motion-off) [data-anim="reveal-mask"].is-visible video {
  transform: scale(1);
}

/* ---- Preferred technique: native CSS scroll-driven animations where
   supported -- no JS observer needed, JS still only adds html.js. ---- */
@supports (animation-timeline: view()) {
  html.js:not(.reduced-motion):not(.motion-off) [data-anim]:not([data-anim="none"]):not([data-anim="stagger-children"]):not([data-anim="parallax"]):not([data-anim="scrub-sequence"]) {
    animation-name: motion-reveal;
    animation-timeline: view();
    animation-range: entry 0% cover 35%;
    animation-fill-mode: both;
    transition: none;
  }

  html.js:not(.reduced-motion):not(.motion-off) [data-anim="reveal-mask"]::after {
    animation-name: motion-reveal-mask;
    animation-timeline: view();
    animation-range: entry 0% cover 35%;
    animation-fill-mode: both;
    transition: none;
  }

  /* Browsers that support scroll-driven animations use this keyframe
     instead of the plain transition-based rules above (it takes over the
     `transform`/`opacity` property entirely while animation-timeline is
     active) -- so a rotated item (product_detail images) needs its own
     keyframe here too, composing rotate() in, or its tilt gets silently
     dropped the same way a plain `transform: rotate()` on the element would
     have overridden the reveal transform. Scoped to the concrete class
     rather than baking rotate(var(--item-rotate, 0deg)) into the shared
     `to` state below for everyone: an identity rotate(0deg), unlike `none`,
     establishes a containing block for any position:fixed descendant, which
     would silently break something like the GSAP pin on product-sandwich.js
     if it were ever nested in a generically-reveal-animated ancestor. */
  /* Same selector chain as the generic rule above plus the class, so this
     wins on specificity ((0,8,1) vs (0,7,1)) rather than relying on source
     order -- a plain `.block-product-detail__image` class selector alone
     is less specific than four :not([data-anim="..."]) clauses and would
     silently lose to the generic rule. */
  html.js:not(.reduced-motion):not(.motion-off) [data-anim]:not([data-anim="none"]):not([data-anim="stagger-children"]):not([data-anim="parallax"]):not([data-anim="scrub-sequence"]).block-product-detail__image {
    animation-name: motion-reveal-rotated;
  }

  /* reveal-down/left/right/zoom-in bauen rotate(var(--item-rotate)) schon
     direkt in ihre eigenen Keyframes ein (siehe unten) -- anders als
     reveal-up brauchen sie deshalb KEINE eigene .block-product-detail__image-
     Variante, sie funktionieren fuer rotierte Items bereits von selbst.
     Selbe lange Selektorkette + der konkrete Wert wie bei der
     .block-product-detail__image-Ausnahme oben, aus demselben Grund:
     ((0,8,1) statt (0,7,1)) noetig, um die generische motion-reveal-Zuweisung
     zu schlagen -- ein kuerzerer Selektor wie [data-anim="reveal-left"]
     allein waere weniger spezifisch als die generische Regel und wuerde
     stumm verlieren. */
  html.js:not(.reduced-motion):not(.motion-off) [data-anim]:not([data-anim="none"]):not([data-anim="stagger-children"]):not([data-anim="parallax"]):not([data-anim="scrub-sequence"])[data-anim="reveal-down"] {
    animation-name: motion-reveal-down;
  }

  html.js:not(.reduced-motion):not(.motion-off) [data-anim]:not([data-anim="none"]):not([data-anim="stagger-children"]):not([data-anim="parallax"]):not([data-anim="scrub-sequence"])[data-anim="reveal-left"] {
    animation-name: motion-reveal-left;
  }

  html.js:not(.reduced-motion):not(.motion-off) [data-anim]:not([data-anim="none"]):not([data-anim="stagger-children"]):not([data-anim="parallax"]):not([data-anim="scrub-sequence"])[data-anim="reveal-right"] {
    animation-name: motion-reveal-right;
  }

  html.js:not(.reduced-motion):not(.motion-off) [data-anim]:not([data-anim="none"]):not([data-anim="stagger-children"]):not([data-anim="parallax"]):not([data-anim="scrub-sequence"])[data-anim="zoom-in"] {
    animation-name: motion-zoom-in;
  }

  @keyframes motion-reveal {
    from { opacity: 0; transform: translateY(var(--anim-distance)) scale(var(--anim-scale-from, 1)); }
    to { opacity: 1; transform: none; }
  }

  @keyframes motion-reveal-rotated {
    from { opacity: 0; transform: translateY(var(--anim-distance)) scale(var(--anim-scale-from, 1)) rotate(var(--item-rotate, 0deg)); }
    to { opacity: 1; transform: rotate(var(--item-rotate, 0deg)); }
  }

  @keyframes motion-reveal-mask {
    from { transform: scaleX(1); }
    to { transform: scaleX(0); }
  }

  @keyframes motion-reveal-down {
    from { opacity: 0; transform: translateY(calc(-1 * var(--anim-distance))) rotate(var(--item-rotate, 0deg)); }
    to { opacity: 1; transform: rotate(var(--item-rotate, 0deg)); }
  }

  @keyframes motion-reveal-left {
    from { opacity: 0; transform: translateX(calc(-1 * var(--anim-distance))) rotate(var(--item-rotate, 0deg)); }
    to { opacity: 1; transform: rotate(var(--item-rotate, 0deg)); }
  }

  @keyframes motion-reveal-right {
    from { opacity: 0; transform: translateX(var(--anim-distance)) rotate(var(--item-rotate, 0deg)); }
    to { opacity: 1; transform: rotate(var(--item-rotate, 0deg)); }
  }

  @keyframes motion-zoom-in {
    from { opacity: 0; transform: scale(1.7) rotate(var(--item-rotate, 0deg)); filter: blur(10px); }
    to { opacity: 1; transform: rotate(var(--item-rotate, 0deg)); filter: blur(0); }
  }
}

html.js:not(.reduced-motion):not(.motion-off) [data-anim="fade"],
html.js:not(.reduced-motion):not(.motion-off) [data-anim="scale-in"] {
  --anim-distance: 0px;
}

html.js:not(.reduced-motion):not(.motion-off) [data-anim="scale-in"] {
  --anim-scale-from: 0.92;
}

/* ---- parallax: continuous transform driven from JS via --parallax-y,
   never a one-shot reveal. Falls back to static (no motion) without JS. ---- */
[data-anim="parallax"] {
  will-change: auto;
}

html.js:not(.reduced-motion):not(.motion-off) [data-anim="parallax"] {
  transform: translateY(var(--parallax-y, 0px));
}
