diff --git a/frontend/src/components/discovery/MasonryGrid.vue b/frontend/src/components/discovery/MasonryGrid.vue index ecc43dc..7375be6 100644 --- a/frontend/src/components/discovery/MasonryGrid.vue +++ b/frontend/src/components/discovery/MasonryGrid.vue @@ -117,22 +117,35 @@ onUnmounted(() => observer && observer.disconnect()) } .fc-masonry__end { text-align: center; padding: 32px 0; } -/* IR-parity stagger fade-in for showcase entry / shuffle. 60ms between - items, 250ms each — matches IR's `itemFadeIn` keyframe (style.css - ~line 1834). Honors prefers-reduced-motion. */ +/* Cascade entry: each tile flips up out of a backward tilt and settles + into place, one at a time — more pronounced than a plain fade so the + showcase reads as an "experience" (operator-flagged 2026-05-28). The + `both` fill holds the hidden/tilted 0% state until each tile's staggered + turn; the cubic-bezier overshoots slightly past flat then settles. + Honors prefers-reduced-motion. Tunables: tilt (-28deg), stagger (70ms), + duration (0.6s). */ @keyframes fc-masonry-item-in { - from { opacity: 0; transform: translateY(12px); } - to { opacity: 1; transform: translateY(0); } + 0% { + opacity: 0; + transform: perspective(1000px) rotateX(-28deg) translateY(26px) scale(0.95); + } + 55% { opacity: 1; } + 100% { + opacity: 1; + transform: perspective(1000px) rotateX(0deg) translateY(0) scale(1); + } } .fc-masonry__item--anim { - animation: fc-masonry-item-in 0.25s ease forwards; - animation-delay: calc(var(--stagger-index, 0) * 60ms); - opacity: 0; + transform-origin: center top; + backface-visibility: hidden; + animation: fc-masonry-item-in 0.6s cubic-bezier(0.34, 1.45, 0.64, 1) both; + animation-delay: calc(var(--stagger-index, 0) * 70ms); } @media (prefers-reduced-motion: reduce) { .fc-masonry__item--anim { animation: none; opacity: 1; + transform: none; } .fc-masonry__item img { transition: none; } .fc-masonry__item:hover img { transform: none; }