Motion bukan dekorasi. Di MASA, motion adalah bahasa — cara app berkomunikasi bahwa sesuatu telah terjadi, sedang terjadi, atau siap terjadi. Tanpa sistem, setiap developer membuat keputusan sendiri dan hasilnya tidak konsisten.
MASA Motion Philosophy: "Calm, purposeful, respectful." Motion di MASA harus tenang seperti brand personality-nya — tidak agresif, tidak berlebihan, tidak mengalihkan dari ibadah dan komunitas. Setiap animasi harus menjawab: apakah ini membantu user memahami apa yang terjadi?
3 Fungsi Motion di MASA
01 · Komunikasi
Feedback & Status
User tahu aksi mereka diterima. Loading, sukses, error — semua dikomunikasikan melalui motion sebelum state change selesai.
02 · Orientasi
Spatial Awareness
User tahu di mana mereka berada dalam app. Page transition membangun mental model navigasi — naik, turun, masuk, keluar.
03 · Emosi
Delight & Trust
Sedekah sukses terasa lebih bermakna dengan spring pop. Streak milestone terasa lebih memuaskan dengan bounce. Trust dibangun melalui konsistensi motion.
Tanpa sistem motion, yang terjadi:
✕ Dev A pakai 300ms ease untuk semua
✕ Dev B pakai 0.5s linear karena default
✕ Dev C tidak pakai animasi sama sekali
✕ Hasil: app terasa "patah" di satu layar, "lambat" di layar lain, "dingin" di layar lain
✕ Tidak ada yang bisa review karena tidak ada standar
5 Motion Principles
Prinsip ini adalah filter keputusan. Sebelum menambah animasi apapun, jawab: apakah sesuai dengan 5 prinsip ini?
P1
Purposeful — Setiap animasi punya alasan
Tidak ada animasi hanya untuk terlihat keren. Setiap motion harus menjawab: ini membantu user melakukan apa? Jika tidak bisa dijawab, hapus animasinya.
Test: "Jika animasi ini dihapus, apakah user lebih bingung?" → Kalau tidak, hapus.
P2
Swift — Lebih cepat dari yang kamu kira
User tidak ingin menunggu animasi selesai. Standard transition: 200–300ms. Di atas 400ms sudah terasa lambat untuk aksi yang sering dilakukan. Animasi yang lambat mengurangi perceived performance.
Rule: Animasi yang diulang ratusan kali sehari harus ≤200ms.
P3
Natural — Ikuti hukum fisika
Objek di alam tidak bergerak linear. Mereka akselerasi dan deselerasi. Easing curve yang baik membuat motion terasa "nyata" dan tidak robotic. Bottom sheet masuk dengan decelerate (sudah cepat, melambat). Notifikasi keluar dengan accelerate (melambat, lalu pergi cepat).
P4
Respectful — Hormati pilihan aksesibilitas user
Beberapa user punya vestibular disorder atau motion sensitivity. Semua animasi WAJIB bisa dimatikan dengan prefers-reduced-motion. Ini bukan optional — ini legal obligation di banyak negara dan etika dasar.
Rule: Setiap animasi HARUS punya fallback di reduced-motion mode.
P5
Consistent — Sama di seluruh app
Bottom sheet selalu slide dari bawah. Success state selalu spring pop. Error selalu shake. Konsistensi membangun muscle memory user — mereka tahu apa yang akan terjadi sebelum terjadi. Ini membangun trust.
Duration Tokens
7 tingkat durasi dari 50ms hingga 1000ms. Pilih berdasarkan seberapa sering animasi diulang dan seberapa besar perubahan visual yang terjadi.
Token
Visual scale
Value
Penggunaan
--dur-instant
50ms
Hover state, focus ring, button pressed state. Respons langsung — user tidak merasakan delay.
--dur-fast
100ms
Toggle switch, checkbox, ripple. Aksi kecil yang dilakukan berkali-kali per sesi.
--dur-normal
200ms
Default untuk sebagian besar UI. Card expand, input focus, tab switch. Terasa natural tanpa menunggu.
--dur-moderate
300ms
Bottom sheet open, dialog appear, page push forward. Perubahan yang lebih besar butuh waktu sedikit lebih lama.
--dur-slow
500ms
Bottom sheet dismiss, large modal, Sedekah success state. Perubahan signifikan yang butuh perhatian user.
--dur-gentle
700ms
Onboarding transitions, splash exit, impact card reveal. Momen yang sengaja dibuat "berasa".
--dur-patient
1000ms
Streak milestone celebration, Qurban confirmation. Momen emosional — user harus benar-benar merasakannya. Gunakan sangat jarang.
Aturan pemilihan durasi:
Aksi harian berulang (toggle, tap, scroll) → 50–200ms
Perubahan layar (push, modal, sheet) → 200–300ms
Momen emosional (sukses, milestone, konfirmasi penting) → 500–1000ms
Jika ragu: mulai dari 200ms, naikkan hanya jika terasa terlalu tiba-tiba.
6 kurva easing. Pilih berdasarkan konteks: objek masuk (decelerate), objek keluar (accelerate), atau objek berpindah dalam layar (standard). Jangan gunakan linear kecuali untuk loading shimmer.
standard DEFAULT
cubic-bezier(0.4, 0.0, 0.2, 1)
Akselerasi lalu deselerasi. Gerakan terasa natural dan responsif. Mulai lambat, cepat di tengah, melambat di akhir.
Tab switch, card expand, chip select, sebagian besar UI transitions
decelerate
cubic-bezier(0.0, 0.0, 0.2, 1)
Masuk cepat, berhenti lembut. Seperti objek yang datang dari jauh dan mendarat. Terasa "hadir".
Bottom sheet open, modal appear, page push (element masuk ke layar)
accelerate
cubic-bezier(0.4, 0.0, 1, 1)
Mulai lambat, pergi cepat. Seperti objek yang pergi dan meninggalkan. Terasa "pergi".
Bottom sheet dismiss, dialog keluar, page pop (element keluar dari layar)
sharp
cubic-bezier(0.4, 0.0, 0.6, 1)
Bergerak di antara dua titik tanpa berhenti di manapun. Untuk toggle yang tidak perlu emphasis.
Drawer open/close, accordion expand, element yang bergerak dari satu state ke state lain
spring DELIGHT
cubic-bezier(0.34, 1.56, 0.64, 1)
Overshoot sedikit lalu settle. Terasa "hidup" dan ekspresif. Gunakan sparingly — untuk momen yang ingin "dirayakan".
Sedekah success, streak milestone, FAB appear, badge counter update
linear
linear
Kecepatan konstan. Terasa robotic untuk transisi. Namun bagus untuk efek yang memang berulang konstan.
Hanya untuk: skeleton shimmer, loading spinner, progress bar indeterminate
Setiap animasi di MASA WAJIB memiliki fallback untuk prefers-reduced-motion: reduce. Ini bukan opsional — ini aksesibilitas dasar dan compliance WCAG 2.1 Level AA.
Siapa yang terdampak: Vestibular disorder, motion sensitivity, epilepsi, atau siapapun yang mengaktifkan "Reduce Motion" di Settings Android/iOS. Di Indonesia, kemungkinan besar ada user MASA dengan kondisi ini terutama dari segmen lansia.
3 Level fallback
Level 1 · Ganti
Transform → Fade
Slide, scale, bounce → simple opacity fade. User masih lihat transisi, tapi tidak ada gerakan yang mengganggu.
Page push, bottom sheet, card expand
Level 2 · Percepat
Normal → Instant
Kurangi duration ke 50ms atau 0ms. Animasi tetap ada tapi hampir tidak terasa.
Gunakan: metode bayar, share, action menu, payment method picker
Flutter PageRouteBuilder:
Semua page transition di MASA menggunakan PageRouteBuilder dengan transitionDuration dan transitionsBuilder yang sudah dikonfigurasi. Jangan gunakan MaterialPageRoute default — transisi-nya tidak sesuai design MASA.
Component Motion
Setiap komponen yang berubah state punya motion yang terdefinisi. Tidak ada "bebas interpretasi" dari dev.
Komponen
Duration
Easing
Apa yang bergerak
Reduced
Iuran VA Error P0 Bug
100ms
--ease-sharp
Input border color: neutral → red. Error text fade in from below.
optional
Button: default → pressed
50ms
--ease-standard
scale: 1.0 → 0.97, opacity: 1 → 0.85
optional
Button: loading state
200ms
--ease-standard
Label fade out, spinner fade in. Width stays constant (no layout shift).
optional
Toggle: off → on
200ms
--ease-standard
Thumb: translateX(0 → 20px). Track: color neutral → blue. Both simultaneous.
Animasi khusus untuk momen-momen yang harus "dirasakan" user — bukan hanya dilihat. Payment sukses, error, streak milestone, donation impact.
Payment Success
Sedekah / Iuran / Donasi berhasil
Sequence (1000ms total):
0ms — backdrop fade in (200ms)
100ms — check circle scale in, spring (500ms)
400ms — title fade+slide up (300ms)
550ms — subtitle fade in (200ms)
700ms — CTA button slide up (300ms)
Haptic (Android/iOS):
HapticFeedback.mediumImpact() Saat check muncul (100ms)
Reduced motion:
Semua transform → fade only. Duration masing-masing → 50ms.
Error State
Payment gagal / VA error
Sequence (600ms total):
0ms — border color: neutral → red (100ms)
50ms — error shake animation (400ms)
100ms — error message fade+slide down (200ms)
0ms — recovery options fade in (200ms, after shake)
Haptic:
HapticFeedback.heavyImpact() Sync dengan shake awal
Note:
Shake hanya 1x. Jangan ulangi tiap kali user tap retry. Sekali cukup untuk komunikasi error.
Habit Streak Milestone
Hari ke-7, ke-30, ke-100
Sequence (1500ms total):
0ms — confetti particle burst (800ms)
200ms — number bounce from old → new (600ms spring)
500ms — milestone card slide up (500ms)
1000ms — "Masya Allah" text fade in (300ms)
1200ms — haptic pattern (3 light pulses)
Tone:
Perayaan yang tenang, tidak berlebihan. Confetti: max 20 partikel, 1 warna. Bukan Duolingo-style celebration yang berlebihan.
Reduced motion:
Hapus confetti dan bounce. Hanya fade in milestone card.
Qurban Distribution Update
Tracking status berubah
Timeline item: dot color transition (neutral → green, 300ms standard) + checkmark scale in (spring, 400ms).
Push notif trigger: saat masuk app, play full sequence.
Background: tidak ada animasi — hanya saat layar aktif.
Component → Token Map
Referensi cepat — untuk setiap komponen, token mana yang digunakan. Copy-paste langsung ke Flutter widget.
Komponen
Action
Duration
Easing
Property yang berubah
Button
Press
--dur-instant (50ms)
--ease-standard
scale 1→0.97, opacity 1→0.85
Button
Loading
--dur-normal (200ms)
--ease-standard
label opacity, spinner opacity
Toggle
Switch
--dur-normal (200ms)
--ease-standard
translateX, background-color
Checkbox
Check
--dur-fast (100ms)
--ease-standard
checkmark scale, border-color
Chip
Select
150ms
--ease-standard
background-color, scale 1→1.03→1
Text Field
Focus
--dur-normal (200ms)
--ease-standard
border-color, box-shadow
Text Field
Error
--dur-fast (100ms) + 400ms shake
--ease-sharp + linear
border-color → shake
Toast
Appear
--dur-normal (200ms)
--ease-decelerate
translateY +16→0, opacity 0→1
Toast
Dismiss
--dur-fast (100ms)
--ease-accelerate
opacity 1→0
Dialog
Open
--dur-normal (200ms)
--ease-decelerate
scale 0.9→1, opacity 0→1
Bottom Sheet
Open
--dur-moderate (300ms)
--ease-decelerate
translateY 100%→0, backdrop 0→0.4
Bottom Sheet
Dismiss
250ms
--ease-accelerate
translateY 0→100%
Page Push
Navigate forward
--dur-moderate (300ms)
--ease-decelerate
translateX +100%→0
Page Pop
Back
250ms
--ease-accelerate
translateX 0→+100%
Skeleton
Shimmer
1500ms ∞
--ease-linear
background-position
Progress Bar
Fill on mount
--dur-slow (500ms)
--ease-decelerate
width 0→target%
FAB
Appear
--dur-moderate (300ms)
--ease-spring
scale 0→1, opacity 0→1
Empty State
Appear (stagger)
--dur-moderate (300ms)
--ease-decelerate
translateY 12→0, opacity 0→1, delay +50ms per item
Sedekah Success
Confirm
--dur-slow (500ms)
--ease-spring
check scale 0→1, card fade+slide
Streak Milestone
Achievement
--dur-gentle (700ms)
--ease-spring
number bounce, confetti, card slide
e-KTAM QR
Flip to QR
--dur-moderate (300ms)
--ease-standard
rotateY 0→90 (first half), 90→0 (second half)
Verification Badge
Pending → Verified
--dur-normal (200ms)
--ease-standard
background-color, checkmark scale 0→1
Motion Anti-patterns
Yang TIDAK boleh dilakukan. Lebih mudah menghindari anti-pattern daripada mendesain dari nol. Kalau ragu — tanya: apakah animasi ini membantu user, atau hanya membuatnya menunggu?
✕ Jangan
Durasi >400ms untuk aksi yang sering dilakukan (toggle, tap button, chip)
Linear easing untuk transisi UI — terasa robotic
Animasi tanpa reduced-motion fallback
Lebih dari 3 elemen beranimasi bersamaan tanpa stagger
Animasi yang memblokir interaksi user (pointer-events: none terlalu lama)
Spring/overshoot untuk error state — terasa tidak serius
Terlalu banyak "delight" animation — merusak kepercayaan
Animasi yang berbeda untuk gesture yang sama di layar berbeda
Animasi infinite loop di konten (bukan loading)
Page transition yang sama untuk forward dan back navigation
✓ Lakukan ini
≤200ms untuk semua aksi yang berulang per sesi
Selalu decelerate untuk "masuk", accelerate untuk "keluar"
Satu property bergerak per momen (tidak scale + rotate + opacity bersamaan)
Stagger 40–60ms antar item dalam list yang muncul bersamaan
Haptic sync dengan visual animation (HapticFeedback)
Spring hanya untuk momen emosional penting (sukses, milestone)
Test setiap animasi di low-end device (Redmi 9A)
Gunakan `AnimatedWidget` atau `ImplicitlyAnimatedWidget` Flutter — bukan manual interpolation
Reduced motion test di setiap PR yang mengubah animasi
Contoh spesifik yang sering salah di MASA context
✕
Sedekah Subuh streak counter: Jangan animasi angka bouncing setiap kali Beranda dibuka. Hanya animasi saat pertama kali user melihat hari ini (setelah sedekah).
✕
Iuran payment: Jangan gunakan spring/bounce untuk payment confirmation. Ini transaksi uang — gunakan standard/decelerate yang serius dan terpercaya.
✕
Al-Qur'an reader: Jangan animasi per-ayat saat scroll. Page-level fade in cukup. Animasi per-ayat mengganggu konsentrasi membaca.
✓
FAB Sedekah: Pulse animation (opacity 1→0.5, 2s infinite) saat user belum pernah sedekah hari ini. Matikan setelah sedekah. Tidak ada pulse di reduced-motion mode.
Flutter Implementation
File-file yang perlu dibuat di lib/core/motion/. Copy-paste dan sesuaikan.
Performance tip: Gunakan RepaintBoundary wrapping animated widget untuk mencegah parent rebuild. Untuk animasi list stagger, gunakan AnimationLimiter dari package flutter_staggered_animations — lebih performant dari manual delay.