@keyframes fadeIn{from{opacity:0}to{opacity:1}}
@keyframes fadeOut{from{opacity:1}to{opacity:0}}
@keyframes slideInRight{from{transform:translateX(100%)}to{transform:translateX(0)}}
@keyframes slideOutLeft{from{transform:translateX(0)}to{transform:translateX(-30%);opacity:.5}}
@keyframes slideInLeft{from{transform:translateX(-100%)}to{transform:translateX(0)}}
@keyframes slideOutRight{from{transform:translateX(0)}to{transform:translateX(100%)}}
@keyframes fadeUp{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:none}}
@keyframes fadeDown{from{opacity:0;transform:translateY(-10px)}to{opacity:1;transform:none}}
@keyframes scaleIn{from{opacity:0;transform:scale(.9)}to{opacity:1;transform:scale(1)}}
@keyframes scaleOut{from{opacity:1;transform:scale(1)}to{opacity:0;transform:scale(.9)}}
@keyframes shake{0%,100%{transform:translateX(0)}25%{transform:translateX(-8px)}50%{transform:translateX(8px)}75%{transform:translateX(-8px)}}
@keyframes spin{to{transform:rotate(360deg)}}
@keyframes pulseGold{0%,100%{box-shadow:0 0 0 0 rgba(255,215,0,.6)}50%{box-shadow:0 0 14px 4px rgba(255,215,0,.35)}}
@keyframes underlineIn{from{transform:scaleX(0)}to{transform:scaleX(1)}}

.anim-fade-in{animation:fadeIn .3s ease-out both}
.anim-fade-up{animation:fadeUp .35s ease-out both}
.anim-scale-in{animation:scaleIn .25s ease-out both}
.anim-slide-in-right{animation:slideInRight .3s ease-out both}
.anim-slide-out-left{animation:slideOutLeft .3s ease-out both}
.anim-shake{animation:shake .35s ease-in-out}
.anim-spin{animation:spin 1s linear infinite}
.anim-pulse-gold{animation:pulseGold 2s ease-in-out infinite}

/* stagger helpers（以 CSS 變數 --i 控制延遲）
   不依賴 JS：元素一掛上就 CSS 自播 fadeUp，動畫結束 opacity 停在 1。
   .visible / .page-loaded 是 IntersectionObserver 用的觀察 hook，不再是顯示前提，
   避免 JS race condition / IO 沒觸發時整片空白。 */
.stagger {
  animation: fadeUp 0.6s cubic-bezier(0.2, 0.8, 0.2, 1) both;
  animation-delay: calc(var(--i, 0) * 80ms);
}

@media (prefers-reduced-motion: reduce){
  *,*::before,*::after{animation:none !important;transition-duration:.001s !important}
}
