# ✅ Checklist Review Motion UI & Micro-interactions Trước Khi Deploy

> **Mục đích:** Đảm bảo animations/micro-interactions hoạt động mượt mà, accessible và performant trước khi đưa lên production.
> **Cách dùng:** Tick từng checkbox, fix issues trước khi deploy. Nếu có mục ❌ → Block deploy.

---

## 📋 Mục lục
1. [CSS Properties An Toàn](#1-css-properties-an-toàn)
2. [Timing & Easing Phù Hợp](#2-timing--easing-phù-hợp)
3. [Prefers-Reduced-Motion](#3-prefers-reduced-motion)
4. [Performance DevTools](#4-performance-devtools)
5. [Browser Support](#5-browser-support)
6. [Mobile Performance](#6-mobile-performance)
7. [Will-change Cleanup](#7-will-change-cleanup)

---

## 1. CSS Properties An Toàn

> **Nguyên tắc:** Chỉ animate các properties không trigger Layout reflow hoặc Paint. Ưu tiên `transform` và `opacity` — chúng chạy trên **Compositor thread**, không block main thread.

### 🟢 Properties Được Khuyến Nghị

- [ ] **Dùng `transform: translate()`** thay vì `top/left/margin` để di chuyển element
  ```css
  /* ❌ Tránh */
  .box { transition: margin-left 300ms; }
  
  /* ✅ Dùng */
  .box { transition: transform 300ms; }
  ```

- [ ] **Dùng `transform: scale()`** thay vì thay đổi `width/height` để phóng to/thu nhỏ
  ```css
  /* ❌ Tránh */
  .btn { transition: width 200ms; }
  
  /* ✅ Dùng */
  .btn { transition: transform 200ms; }
  ```

- [ ] **Dùng `opacity`** thay vì `visibility` hoặc `display` cho fade effects
  ```css
  /* ❌ Tránh */
  .modal { transition: visibility 300ms; }
  
  /* ✅ Dùng */
  .modal { transition: opacity 300ms; }
  ```

- [ ] **Dùng `transform: rotate()`** thay vì thay đổi border-radius phức tạp cho spin effects

- [ ] **Dùng `filter`** (blur, brightness) cẩn thận — paint cost cao, chỉ dùng khi cần thiết

### 🔴 Properties Cần Tránh Animate

- [ ] **Kiểm tra không animate `width`, `height`** → Trigger layout reflow
- [ ] **Kiểm tra không animate `padding`, `margin`** → Trigger layout reflow
- [ ] **Kiểm tra không animate `border-width`** → Trigger layout + paint
- [ ] **Kiểm tra không animate `box-shadow`** trực tiếp (dùng `filter: drop-shadow` hoặc pseudo-element trick thay thế)
  ```css
  /* ✅ Trick: animate opacity của pseudo-element chứa shadow */
  .card::after {
    content: '';
    box-shadow: 0 20px 40px rgba(0,0,0,0.2);
    opacity: 0;
    transition: opacity 300ms;
  }
  .card:hover::after { opacity: 1; }
  ```
- [ ] **Kiểm tra không animate `background-color`** trong các list dài (>50 items) — dùng overlay thay thế
- [ ] **Kiểm tra không animate `font-size`** → Trigger layout reflow

### 📊 Bảng Tham Khảo Nhanh

| Property | Trigger | Có Nên Animate? |
|---|---|---|
| `transform` | Composite | ✅ Luôn luôn |
| `opacity` | Composite | ✅ Luôn luôn |
| `filter` | Paint | ⚠️ Cẩn thận |
| `background-color` | Paint | ⚠️ Ít elements |
| `color` | Paint | ⚠️ Ít elements |
| `width/height` | Layout | ❌ Tránh |
| `margin/padding` | Layout | ❌ Tránh |
| `top/left/right/bottom` | Layout | ❌ Tránh |

---

## 2. Timing & Easing Phù Hợp

> **Nguyên tắc:** Duration quá ngắn → người dùng không kịp nhận ra. Quá dài → cảm giác chậm chạp, frustrating. Mỗi loại animation có "thời gian vàng" riêng.

### ⚡ Micro-feedback (100–200ms)

*Phản hồi tức thì cho user action — button click, checkbox, toggle*

- [ ] **Button hover state:** 100–150ms
  ```css
  .btn { transition: transform 150ms ease-out, background-color 150ms ease-out; }
  ```
- [ ] **Button click/press feedback:** 100ms
  ```css
  .btn:active { transform: scale(0.97); transition-duration: 100ms; }
  ```
- [ ] **Checkbox/radio toggle:** 150–200ms
- [ ] **Tooltip appear:** 100–150ms (disappear có thể nhanh hơn: 80ms)
- [ ] **Icon state change** (like, bookmark): 150–200ms
- [ ] **Input focus ring:** 150ms

- [ ] ✅ **Verify:** Không có micro-feedback nào vượt quá 200ms

### 🔄 UI Transitions (200–300ms)

*Chuyển đổi trạng thái UI, dropdown, modal, accordion*

- [ ] **Dropdown menu open:** 200–250ms `ease-out`
  ```css
  .dropdown { transition: opacity 200ms ease-out, transform 200ms ease-out; }
  ```
- [ ] **Dropdown menu close:** 150–200ms (close nhanh hơn open)
- [ ] **Accordion expand/collapse:** 250–300ms `ease-in-out`
- [ ] **Tab switching:** 200ms
- [ ] **Toast/Snackbar appear:** 250ms `ease-out` (slide + fade)
- [ ] **Sidebar toggle:** 250–300ms
- [ ] **Card flip:** 300ms
- [ ] **Skeleton loading → content:** 200ms fade

- [ ] ✅ **Verify:** Enter animations dùng `ease-out`, Exit animations dùng `ease-in`

### 📄 Page & Layout Animations (300–500ms)

*Chuyển trang, modal lớn, onboarding flows*

- [ ] **Modal/Dialog open:** 300ms `cubic-bezier(0.34, 1.56, 0.64, 1)` (có slight bounce)
- [ ] **Modal/Dialog close:** 250ms `ease-in` (không cần bounce khi đóng)
- [ ] **Page transition:** 300–400ms
- [ ] **Drawer/Sheet open:** 350ms `ease-out`
- [ ] **Full-screen overlay:** 400ms
- [ ] **Onboarding step transition:** 400–500ms
- [ ] **Hero section entrance animation:** 400–600ms (stagger nếu nhiều elements)

- [ ] ✅ **Verify:** Không có page animation nào vượt quá 600ms (trừ trường hợp đặc biệt có chủ đích)

### 🎯 Easing Reference

- [ ] **Kiểm tra dùng đúng easing curve:**

```css
/* Vào màn hình (Enter) - Bắt đầu nhanh, kết thúc chậm */
.enter { transition-timing-function: ease-out; }

/* Ra khỏi màn hình (Exit) - Bắt đầu chậm, kết thúc nhanh */
.exit { transition-timing-function: ease-in; }

/* Di chuyển trong màn hình (Move) */
.move { transition-timing-function: ease-in-out; }

/* Bounce/Spring effect cho modal, popup */
.spring { transition-timing-function: cubic-bezier(0.34, 1.56, 0.64, 1); }

/* Snappy - Cảm giác responsive */
.snappy { transition-timing-function: cubic-bezier(0.2, 0, 0, 1); }
```

- [ ] **Không dùng `linear`** cho UI transitions (trừ loading spinners, progress bars)
- [ ] **Không dùng `ease`** mặc định — quá generic, hãy chọn curve phù hợp
- [ ] **Stagger delay** cho list items: 30–50ms mỗi item, tối đa 5–7 items có stagger

---

## 3. Prefers-Reduced-Motion

> **Nguyên tắc:** Một số người dùng bị ảnh hưởng bởi motion (vestibular disorders, epilepsy). Đây là yêu cầu **accessibility bắt buộc**, không phải optional.

### 🔧 Implementation Check

- [ ] **Global reduced-motion reset có trong stylesheet:**
  ```css
  @media (prefers-reduced-motion: reduce) {
    *,
    *::before,
    *::after {
      animation-duration: 0.01ms !important;
      animation-iteration-count: 1 !important;
      transition-duration: 0.01ms !important;
      scroll-behavior: auto !important;
    }
  }
  ```

- [ ] **Hoặc dùng approach "motion-safe" — Enable animation chỉ khi user OK:**
  ```css
  /* Chỉ add animation khi user không có preference reduce */
  @media (prefers-reduced-motion: no-preference) {
    .animated-element {
      transition: transform 300ms ease-out;
    }
  }
  ```

- [ ] **JavaScript animations cũng check:**
  ```javascript
  const prefersReducedMotion = window.matchMedia(
    '(prefers-reduced-motion: reduce)'
  ).matches;
  
  if (!prefersReducedMotion) {
    // Run animation
    element.animate([...], { duration: 300 });
  } else {
    // Skip animation, jump to end state
    element.style.opacity = '1';
  }
  ```

- [ ] **GSAP/Framer Motion/AOS:** Kiểm tra config có handle `prefers-reduced-motion`
  ```javascript
  // GSAP
  if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
    gsap.globalTimeline.timeScale(0);
  }
  
  // Framer Motion - dùng useReducedMotion hook
  const shouldReduceMotion = useReducedMotion();
  ```

### 🧪 Test Steps

- [ ] **Test trên macOS:** System Preferences → Accessibility → Display → Reduce Motion ✓
- [ ] **Test trên Windows:** Settings → Ease of Access → Display → Show animations ✗
- [ ] **Test trên iOS:** Settings → Accessibility → Motion → Reduce Motion ✓
- [ ] **Test trên Android:** Settings → Accessibility → Remove animations ✓
- [ ] **Test bằng DevTools:** Chrome DevTools → Rendering → Emulate CSS media: `prefers-reduced-motion: reduce`
- [ ] **Verify:** UI vẫn functional và understandable khi tắt animations
- [ ] **Verify:** Không có content bị hidden/inaccessible khi tắt animations (e.g., fade-in content không bao giờ visible)

---

## 4. Performance DevTools

> **Nguyên tắc:** Target 60fps (16.67ms/frame). Mọi animation drop dưới 60fps đều có thể cảm nhận được. Dùng Chrome DevTools để diagnose.

### 🎬 FPS Check

- [ ] **Mở Chrome DevTools → Rendering tab → Frame Rendering Stats**
- [ ] **Chạy tất cả animations** trong khi FPS counter đang hiển thị
- [ ] ✅ **FPS phải ≥ 55fps** trong suốt animation (không drop dưới 50fps)
- [ ] ❌ **Flag nếu FPS drop xuống <30fps** — Must fix trước deploy
- [ ] **Test trên CPU throttling 4x** (DevTools → Performance → CPU throttling) để simulate low-end devices

```
🎯 Target FPS benchmarks:
- Desktop: 60fps liên tục
- Laptop (battery): ≥ 55fps
- Mobile high-end: ≥ 55fps  
- Mobile mid-range (với 4x throttle): ≥ 30fps
```

- [ ] **Dùng `chrome://tracing`** cho phân tích sâu hơn nếu cần

### ⏱️ Long Tasks Check

- [ ] **Mở Performance tab → Record** khi animations đang chạy
- [ ] **Tìm các Long Tasks** (màu đỏ, >50ms) trong Main thread
- [ ] **Kiểm tra:** Không có Long Tasks nào xảy ra trong lúc animation đang chạy
- [ ] **Nếu có Long Task:** Tách thành smaller tasks hoặc dùng `requestIdleCallback`
  ```javascript
  // Thay vì làm heavy work trong animation callback
  requestAnimationFrame(() => {
    // Chỉ làm animation work ở đây
    element.style.transform = `translateX(${x}px)`;
  });
  
  // Defer heavy computation
  requestIdleCallback(() => {
    // Heavy data processing ở đây
    processLargeDataset();
  });
  ```
- [ ] **Verify:** Main thread không bị blocked trong animation duration

### 🔄 Layout Reflow Check

- [ ] **Mở Performance tab → Record** → Tìm màu **tím (purple) = Recalculate Style** và **màu vàng (yellow) = Layout**
- [ ] **Verify:** Không có Layout events trong animation frames
- [ ] **Kiểm tra Layers panel** (DevTools → Layers) để xem composite layers
  - Elements đang animate nên có layer riêng
  - Không có quá nhiều layers không cần thiết (memory overhead)

- [ ] **Detect forced synchronous layout** (Layout Thrashing):
  ```javascript
  // ❌ Layout Thrashing - đọc rồi viết xen kẽ
  elements.forEach(el => {
    const height = el.offsetHeight; // Read (force layout)
    el.style.height = height + 10 + 'px'; // Write (invalidate)
  });
  
  // ✅ Batch reads rồi batch writes
  const heights = elements.map(el => el.offsetHeight); // All reads
  elements.forEach((el, i) => {
    el.style.height = heights[i] + 10 + 'px'; // All writes
  });
  ```

- [ ] **Dùng extension "Layout Shift" trong Lighthouse** để check CLS (Cumulative Layout Shift)
- [ ] ✅ **CLS score ≤ 0.1** (Good) — Animations không được gây layout shift

### 🎨 Paint Check

- [ ] **DevTools → Rendering → Paint Flashing** — Bật lên và chạy animations
- [ ] **Verify:** Chỉ có elements đang animate bị highlight (màu xanh lá)
- [ ] **Flag nếu:** Cả page bị repaint khi chỉ một element animate → Cần investigate

### 📊 Memory Check

- [ ] **Performance tab → Memory** — Record và chạy animations lặp lại
- [ ] **Verify:** Không có memory leak (heap size tăng liên tục không giảm)
- [ ] **Đặc biệt check:** Infinite animations, scroll-based animations, event listeners

---

## 5. Browser Support Check

> **Nguyên tắc:** Test đủ browsers trước deploy. Không phải để animations giống nhau 100%, mà để không bị broken.

### 🌐 Desktop Browsers

- [ ] **Chrome (latest 2 versions)** — Primary test browser
  - [ ] Tất cả animations chạy đúng
  - [ ] DevTools không có warnings
  
- [ ] **Firefox (latest 2 versions)**
  - [ ] `backdrop-filter` fallback nếu dùng (Firefox support hạn chế)
  - [ ] CSS custom properties trong animations hoạt động
  - [ ] `@keyframes` animations không bị glitch
  
- [ ] **Safari (latest 2 versions)** ⚠️ *Thường có nhiều issues nhất*
  - [ ] `-webkit-` prefix cho các properties cần thiết
  - [ ] `transform3d` và `translateZ(0)` hacks nếu cần
  - [ ] `overflow: hidden` + `border-radius` + animation không bị bug (Safari classic bug)
  - [ ] `position: sticky` animations hoạt động đúng
  - [ ] Web Animations API support check
  
- [ ] **Edge (Chromium - latest 2 versions)**
  - [ ] Giống Chrome nhưng vẫn cần verify

### 📱 Mobile Browsers

- [ ] **Chrome Android**
- [ ] **Safari iOS** (test thực tế trên device, không chỉ simulator)
  - [ ] Touch interactions có animation
  - [ ] Scroll-based animations không lag
  - [ ] `-webkit-overflow-scrolling: touch` nếu cần

### 🔧 Feature Detection

- [ ] **Không dùng browser sniffing** — Dùng feature detection:
  ```javascript
  // ✅ Feature detection
  if ('animate' in element) {
    // Dùng Web Animations API
  } else {
    // Fallback CSS class toggle
  }
  
  // Check CSS property support
  if (CSS.supports('backdrop-filter', 'blur(10px)')) {
    // Apply glassmorphism
  }
  ```

- [ ] **CSS @supports cho progressive enhancement:**
  ```css
  /* Base style không có animation */
  .card { opacity: 1; }
  
  /* Enhanced với animation nếu support */
  @supports (animation: none) {
    .card {
      opacity: 0;
      animation: fadeIn 300ms ease-out forwards;
    }
  }
  ```

### 📋 Compatibility Quick Check

| Feature | Chrome | Firefox | Safari | Edge |
|---|---|---|---|---|
| CSS Transitions | ✅ | ✅ | ✅ | ✅ |
| CSS Animations | ✅ | ✅ | ✅ | ✅ |
| CSS Transform | ✅ | ✅ | ✅ | ✅ |
| Web Animations API | ✅ | ✅ | ✅ 13.1+ | ✅ |
| `backdrop-filter` | ✅ | ✅ 103+ | ✅ | ✅ |
| `offset-path` | ✅ | ✅ | ⚠️ | ✅ |
| `@starting-style` | ✅ 117+ | ❌ | ✅ 17.5+ | ✅ |

- [ ] **Dùng caniuse.com** để verify features đang dùng với target browser support của project
- [ ] **Thêm autoprefixer** vào build pipeline nếu chưa có

---

## 6. Mobile Performance Check

> **Nguyên tắc:** Mobile devices có CPU/GPU yếu hơn desktop 5–10x. Animations mượt trên desktop có thể lag trên mobile. Luôn test trên real device.

### 📱 Real Device Testing

- [ ] **Test trên ít nhất 1 low-end Android device** (hoặc 4x CPU throttle trong DevTools)
- [ ] **Test trên iPhone (Safari)** — iOS Safari có nhiều quirks riêng
- [ ] **Viewport size:** Test cả portrait và landscape orientation

### 👆 Touch Interactions

- [ ] **Touch feedback ≤ 100ms** — Mọi touch action phải có visual feedback ngay lập tức
- [ ] **Tap highlight tùy chỉnh** hoặc disabled nếu có custom animation:
  ```css
  .btn {
    -webkit-tap-highlight-color: transparent;
    /* Thay bằng custom feedback */
  }
  .btn:active { transform: scale(0.97); }
  ```
- [ ] **Touch target ≥ 44×44px** cho tất cả interactive elements (WCAG guideline)
- [ ] **Không có hover-only states** — Mobile không có hover, cần touch equivalents
- [ ] **Swipe gestures** (nếu có) không conflict với browser native gestures

### 📜 Scroll Performance

- [ ] **Scroll-based animations** dùng `IntersectionObserver` thay vì `scroll` event listener:
  ```javascript
  // ❌ Scroll event - runs on main thread, blocks scrolling
  window.addEventListener('scroll', () => {
    updateAnimation(); // Heavy work
  });
  
  // ✅ IntersectionObserver - efficient, off main thread
  const observer = new IntersectionObserver((entries) => {
    entries.forEach(entry => {
      if (entry.isIntersecting) {
        entry.target.classList.add('animate');
      }
    });
  }, { threshold: 0.1 });
  ```

- [ ] **Nếu bắt buộc dùng scroll event:** Throttle/debounce + `requestAnimationFrame`:
  ```javascript
  let ticking = false;
  window.addEventListener('scroll', () => {
    if (!ticking) {
      requestAnimationFrame(() => {
        updateAnimation();
        ticking = false;
      });
      ticking = true;
    }
  }, { passive: true }); // passive: true quan trọng!
  ```

- [ ] **`{ passive: true }`** cho scroll và touch event listeners (không gọi `preventDefault`)
- [ ] **Parallax effects** disabled hoặc reduced trên mobile (performance cost cao)
- [ ] **CSS scroll snap** animations smooth và không gây layout shifts

### 🔋 Battery & Resources

- [ ] **Infinite animations** (loading spinners) dừng khi not visible:
  ```javascript
  const observer = new IntersectionObserver((entries) => {
    entries.forEach(entry => {
      const spinner = entry.target;
      if (entry.isIntersecting) {
        spinner.style.animationPlayState = 'running';
      } else {
        spinner.style.animationPlayState = 'paused';
      }
    });
  });
  observer.observe(spinnerElement);
  ```

- [ ] **Reduce animation complexity** trên mobile nếu cần (dùng `@media (max-width)` hoặc feature query)
- [ ] **Không có animations** chạy khi app ở background (Page Visibility API):
  ```javascript
  document.addEventListener('visibilitychange', () => {
    if (document.hidden) {
      pauseAllAnimations();
    } else {
      resumeAllAnimations();
    }
  });
  ```

### 🌡️ Thermal Throttling Check

- [ ] **Test sau 10–15 phút sử dụng liên tục** — Device nóng → CPU throttle → animations lag
- [ ] **Animations vẫn acceptable** khi device bị thermal throttle

---

## 7. Will-change Cleanup

> **Nguyên tắc:** `will-change` là "gợi ý" cho browser tạo composite layer. Dùng đúng → tăng performance. Dùng sai hoặc thừa → tốn VRAM, giảm performance tổng thể.

### ✅ Dùng Will-change Đúng Cách

- [ ] **Chỉ apply `will-change` ngay trước khi animation xảy ra** — Remove sau khi xong:
  ```javascript
  element.addEventListener('mouseenter', () => {
    element.style.willChange = 'transform'; // Prepare
  });
  
  element.addEventListener('animationend', () => {
    element.style.willChange = 'auto'; // Cleanup
  });
  ```

- [ ] **Hoặc dùng CSS với :hover pattern** (browser tự manage):
  ```css
  /* Chỉ tạo layer khi cần */
  .card:hover {
    will-change: transform;
  }
  ```

- [ ] **Với CSS animations — set/remove qua class:**
  ```css
  .will-animate {
    will-change: transform, opacity;
  }
  /* Remove class sau khi animation hoàn thành */
  ```

### 🧹 Cleanup Checklist

- [ ] **Scan toàn bộ CSS files** — Tìm tất cả `will-change` declarations
  ```bash
  # Terminal command để tìm
  grep -r "will-change" ./src --include="*.css" --include="*.scss"
  ```

- [ ] **Tìm và xóa `will-change: all`** — Không bao giờ dùng `all`
  ```css
  /* ❌ Tệ nhất có thể làm */
  .element { will-change: all; }
  
  /* ✅ Specify chính xác property */
  .element { will-change: transform; }
  ```

- [ ] **Kiểm tra không có `will-change` trên static elements** (elements không animate)

- [ ] **Kiểm tra không có `will-change` trên quá nhiều elements cùng lúc:**
  ```css
  /* ❌ Sẽ tạo hàng trăm layers nếu list dài */
  .list-item { will-change: transform; }
  
  /* ✅ Chỉ apply khi interact */
  .list-item:hover { will-change: transform; }
  ```

- [ ] **Kiểm tra `will-change` không được set trong JavaScript và quên remove:**
  ```javascript
  // Tìm trong JS files
  // grep -r "willChange" ./src --include="*.js" --include="*.ts"
  ```

### 🔍 DevTools Layers Audit

- [ ] **Mở Chrome DevTools → Layers panel**
- [ ] **Đếm số layers** — Quá nhiều layers là dấu hiệu of `will-change` abuse
- [ ] **Verify:** Chỉ elements đang animate mới có composite layer riêng
- [ ] **Memory size của layers** không quá lớn (xem trong Layers panel)
- [ ] **Sau khi animations xong:** Layers của animated elements nên được release

### ⚠️ Will-change Alternatives

- [ ] **Xem xét dùng `transform: translateZ(0)` hoặc `translate3d(0,0,0)** như legacy hack (nhưng `will-change` preferable hơn):
  ```css
  /* Legacy hack - vẫn works nhưng ít explicit hơn will-change */
  .animated { transform: translateZ(0); }
  
  /* Modern approach */
  .animated { will-change: transform; }
  ```

- [ ] **Với Framer Motion/React Spring:** Check library có tự cleanup không, tránh manual `will-change`

---

## 🚀 Final Deploy Checklist

> Tick tất cả mục này trước khi bấm deploy

### Critical (❌ Không deploy nếu fail)

- [ ] Không có animation nào trigger layout reflow trong main loop
- [ ] FPS ≥ 55fps trên desktop, ≥ 30fps trên mid-range mobile (4x throttle)
- [ ] `prefers-reduced-motion` được implement và test
- [ ] Không có Long Tasks (>50ms) trong animation frames
- [ ] Không có `will-change: all` trong codebase

### Important (⚠️ Fix sớm nhất có thể)

- [ ] Tất cả timing values trong range khuyến nghị
- [ ] Easing curves phù hợp với loại animation
- [ ] Test pass trên Chrome, Firefox, Safari
- [ ] Touch feedback ≤ 100ms trên mobile
- [ ] Scroll event listeners có `passive: true`
- [ ] Infinite animations pause khi off-screen

### Nice to Have (📝 Backlog nếu không kịp)

- [ ] Test trên low-end Android real device
- [ ] Thermal throttling test
- [ ] Memory leak test với extended session
- [ ] CLS score ≤ 0.1 verified in Lighthouse

---

## 📚 Quick Reference

```
⚡ TIMING CHEAT SHEET
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Micro-feedback   │ 100–200ms │ Buttons, toggles, icons
UI Transitions   │ 200–300ms │ Dropdowns, tooltips, tabs  
Page Animations  │ 300–500ms │ Modals, page changes
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Enter  → ease-out  │ Exit → ease-in  │ Move → ease-in-out

🟢 SAFE PROPERTIES
transform: translate | scale | rotate | skew
opacity

🔴 AVOID ANIMATING  
width | height | margin | padding | top | left | border-width

🎯 PERFORMANCE TARGETS
Desktop FPS: 60fps
Mobile FPS:  ≥30fps (4x throttle)
Long Tasks:  0 trong animation frames
CLS Score:   ≤ 0.1
```

---

*Checklist version 1.0 — Cập nhật theo Web Vitals và browser support mới nhất*
*Tham khảo: web.dev/animations, MDN Web Docs, Chrome DevTools documentation*