Motion · preview

Spec đã viết ở .memory/shared/motion-spec.md. Tokens ở design-tokens.json. Trang này để bạn THẤY các easing/duration trực quan, click thử card flip, bật reduced-motion. Duyệt rồi mình implement VIỆC 2.

VIỆC 1 · APPROVAL
SPEC + TOKENS + PREVIEW
A

Timing & easing tokens

5 duration · 3 easing · 3 stagger. Click button để xem chuyển động ở mỗi duration. Click "Replay" để chạy lại từng easing.

FOUNDATION
5 + 3 + 3
Duration scale
CLICK TO PREVIEW
// runner di chuyển hết track với duration đã chọn · linear easing để bạn cảm rõ thời gian
ease-out-soft
cubic-bezier(0.16, 1, 0.3, 1)
Dùng khi element xuất hiện (fade-in, slide-up). Bùng nhanh đầu, thả nhẹ về cuối — cảm giác "đến nơi" rõ.
ease-in-out-smooth
cubic-bezier(0.65, 0, 0.35, 1)
Dùng cho chuyển động qua lại (card flip, drawer mở/đóng). Smooth cả 2 đầu — không hụt hẫng.
ease-spring
cubic-bezier(0.34, 1.56, 0.64, 1)
Dùng cho micro-interaction (hover, tap). Nảy nhẹ ~6% — feel "alive" mà không bouncy thái quá.
Stagger scale
3 SPEED · CLICK REPLAY
// stagger-tight · 50ms · list dài (≥8)
A
B
C
D
E
F
G
H
// stagger-normal · 80ms · DEFAULT (card grid, nav)
A
B
C
D
E
F
G
H
// stagger-loose · 120ms · hero element ít (3-5)
A
B
C
D
E
F
G
H
B

Card flip · rút bài

rotateY 180° + perspective 1000px + ease-in-out-smooth 600ms. Click lá để flip. 3 lá fan demo stagger 200ms.

CORE INTERACTION
rotateY · 600ms
Single card · click to flip
600ms · ease-in-out-smooth
XVII
Ngôi Sao
/* perspective ở wrapper · transform-style: preserve-3d · backface-visibility: hidden cả 2 face */ .flip-card { transform-style: preserve-3d; transition: transform 0.6s var(--ease-in-out-smooth); } .flip-card.flipped { transform: rotateY(180deg); }
3-card spread · staggered flip · 200ms delay
FAN · CLICK ANY
QK
Gã Khờ
HT
Ngôi Sao
TL
Mặt Trời
/* Stagger: nth-child transition-delay 0 / 200ms / 400ms */ .flip-deck.staggered .flip-card:nth-child(2) { transition-delay: 200ms; } .flip-deck.staggered .flip-card:nth-child(3) { transition-delay: 400ms; }
C

Loaders · cosmic

2 variant — light (SEO pages, pure CSS pulse) + full (interactive pages, 3-orbit constellation).

LOADING STATE
2 VARIANT
Light · pulse sparkle
SEO · PURE CSS
@keyframes pulseSoft { 0%,100% { opacity:.5; transform:scale(.9); } 50% { opacity:1; transform:scale(1.05); } } .loader-light svg { animation: pulseSoft 1.8s var(--ease-in-out-smooth) infinite; }
Full · 3-orbit constellation
INTERACTIVE · FRAMER
Vũ trụ đang trả lời...
// 3 orbit cách nhau 0.83s (= 2.5s/3) · linear · infinite @keyframes orbit { from { transform: rotate(0) translateX(38px); } to { transform: rotate(360deg) translateX(38px) rotate(-360deg); } }
D

Page transition

Fade + slide-up 12px · 300ms · ease-out-soft. Click nav để chuyển page mô phỏng.

ROUTE CHANGE
FRAMER + LENIS
Mô phỏng page transition
CLICK NAV BUTTON

Soi mình qua những lá bài

Welcome to SoiTarot. Hôm nay bộ bài muốn nói gì với bạn?

Bộ Ẩn Chính

22 chiếc gương từ Gã Khờ đến Thế Giới. Tap để khám phá.

12 cung hoàng đạo

Sun · Moon · Rising. Không tử vi — chỉ archetype.

// app/layout.tsx — AnimatePresence + Lenis <motion.main key={pathname} initial={{ opacity: 0, y: 12 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: -8 }} transition={{ duration: 0.3, ease: [0.16, 1, 0.3, 1] }}>{children}</motion.main>
E

Hover & micro-interaction

Hover button + card + link để xem. Tất cả dùng ease-spring · duration-fast (200ms).

FEEL · DELIGHT
SPRING · 200ms
3 element · hover thật để cảm
ease-spring
BUTTON · scale 1.02 + glow
Ngôi Sao
XVII · hy vọng
CARD · lift -4px + glow
trực giác LINK · underline slide
/* Button: scale 1.02 + box-shadow gold glow · spring */ .micro-btn { transition: transform var(--duration-fast) var(--ease-spring), box-shadow var(--duration-fast) var(--ease-spring); } .micro-btn:hover { transform: scale(1.02); box-shadow: 0 0 24px rgba(212,165,116,0.45); } /* Card: lift translateY(-4px) + border gold + glow */ /* Link: ::after underline slide từ width:0 → 100% */
F

Scroll reveal

Cuộn xuống trong pane bên dưới — mỗi block fade-up khi vào viewport. once: true · margin -80px.

whileInView
FRAMER + IO
Scroll demo · cuộn xuống trong khung
IntersectionObserver
Block 1 · trên cùng (already visible)

Block này đã hiển thị ngay từ đầu — initial state set thủ công.

Block 2 · cuộn xuống một chút

Sẽ fade-up 20px khi vào viewport (margin -40px trong scroll pane).

Block 3 · Lá Gã Khờ

Khởi đầu mới. Niềm tin vào hành trình. Khi bạn đứng trên vách núi và biết mình phải nhảy.

Block 4 · Lá Pháp Sư

Sáng tạo. Bốn nguyên tố trong tay. Bạn có đủ công cụ — câu hỏi là dám dùng hay không.

Block 5 · Lá Nữ Tư Tế

Trực giác. Sự tĩnh lặng. Đôi khi câu trả lời không phải hỏi — mà phải nghe.

Block 6 · Lá Hoàng Hậu

Nuôi dưỡng. Đất Mẹ. Sáng tạo từ sự dồi dào, không phải từ thiếu thốn.

Block 7 · Lá Tử Thần

Tử Thần không phải về cái chết. Là về chuyển hoá — cái cũ chết để cái mới sống.

Block 8 · Lá Ngôi Sao

Sau cơn bão, hy vọng. Đổ nước mát xuống đất nứt nẻ. Lành lại.

// Framer Motion equivalent <motion.div initial={{ opacity: 0, y: 20 }} whileInView={{ opacity: 1, y: 0 }} viewport={{ once: true, margin: "-80px" }} transition={{ duration: 0.5, ease: [0.16, 1, 0.3, 1] }}>
G

Reduced motion & perf

Bật toggle dưới đây — toàn trang sẽ chuyển sang reduced-motion mode (duration = 0). Test card flip, hover, scroll reveal ngay.

A11Y · CORE WEB VITALS
BẮT BUỘC
Toggle prefers-reduced-motion
Khi BẬT — toàn site về duration 0. State change vẫn visible (color, opacity). Hover/card/scroll vẫn hoạt động — chỉ là instant.
Performance budget
PER PAGE TYPE
// SEO PAGES · 15K programmatic
Max 3 anim · 500ms cap
  • Hero text: opacity-only fade, 200ms
  • Scroll reveal: y ≤ 16px (CLS < 0.1)
  • NO card flip · NO heavy loader
// INTERACTIVE PAGES · rút bài / kết quả
Max 6 anim · 800ms cap
  • Card flip OK · stagger OK
  • Full cosmic loader OK
  • Hero reveal OK (slower duration)

Spec này OK không?

Nếu OK → mình làm VIỆC 2: build component FadeIn / SlideUp / StaggerChildren / ScrollReveal / CardFlip / CosmicLoader / PageTransition + Lenis wrap + reduced-motion handler + Tailwind config + ghi nhận ở tech-decisions.md.

👍 OK · build VIỆC 2
Sửa: "đổi duration", "thêm ease X"...