Swoosh/docs/superpowers/specs/2026-03-19-micro-animations-design.md
Paweł Orzech 31b04e549c
docs: add micro-animations design spec
Define 19 micro-animations + 5 navigation transitions across all
screens with expressive/bouncy style and shared SwooshMotion specs.
2026-03-19 10:28:17 +01:00

7.8 KiB

Micro-Animations Design — Swoosh

Date: 2026-03-19 Status: Approved Style: Expressive & playful — bouncy springs, overshoot, lively feedback

Overview

Add 19 micro-animations + 5 navigation transitions across all screens to make the app feel alive. Create a shared SwooshMotion object with predefined animation specs ensuring consistent character throughout the app.

Shared Animation Specs — SwooshMotion

Central object in a new file ui/animation/SwooshMotion.kt providing reusable animation specifications:

Name Type Parameters Use case
Bouncy Spring dampingRatio=0.55, stiffness=400 FAB, buttons, chips
Snappy Spring dampingRatio=0.7, stiffness=800 Expand/collapse, dialogs
Gentle Spring dampingRatio=0.8, stiffness=300 Cards, content reveal
Quick Tween 200ms, FastOutSlowInEasing Fade, color transitions
StaggerDelay Offset 50ms per item List item entrances

Feed Screen — 7 animations

1. FAB entrance (SPRING/Bouncy)

When the feed screen opens, the FAB scales from 0 to 1 with Bouncy spring. Visible overshoot — the FAB "pops" onto screen.

  • Compose API: animateFloatAsState with Bouncy spring on scale modifier
  • Trigger: LaunchedEffect(Unit) sets target to 1f

2. FAB press (SPRING/Bouncy)

On tap, FAB shrinks to 85% and springs back to 100%.

  • Compose API: Modifier.pointerInput detecting press → animateFloatAsState scale 0.85f → 1f
  • Spring spec: Bouncy

3. Post cards staggered entrance (SLIDE/Gentle)

Cards slide in from bottom with cascading delay — 50ms per item. "Waterfall" effect.

  • Compose API: AnimatedVisibility per item with slideInVertically + fadeIn
  • Delay: StaggerDelay * index (capped at visible items, ~8 max)
  • Only on initial load, not on scroll-append

4. "Show more" expand (SPRING/Snappy)

Card height animates with spring when text expands/collapses.

  • Compose API: Modifier.animateContentSize(animationSpec = Snappy)
  • Text content enters with fadeIn

5. Empty state (FADE/Quick + scale)

"No posts yet" icon and text fade in with subtle scale from 0.9 to 1.0.

  • Compose API: AnimatedVisibility with fadeIn + scaleIn(initialScale = 0.9f)

6. Queue status chip (BOUNCE/Bouncy)

During upload: chip pulses (infinite alpha animation 0.6→1.0). On status change (success/fail): bounce scale + color crossfade.

  • Compose API: rememberInfiniteTransition for pulse; animateColorAsState + scale bounce on status change
  • Color transition: Quick tween

7. Snackbar error (SLIDE/Snappy)

Slides in from bottom with slight overshoot. Fades out on timeout.

  • Compose API: AnimatedVisibility with slideInVertically(initialOffsetY = { it }) + Snappy spring
  • Exit: fadeOut with Quick tween

Composer Screen — 6 animations

8. Image preview (SCALE/Bouncy)

After picking an image, the preview scales from 0 with bouncy spring. Close button ("X") rotates in (0°→360° with fade).

  • Compose API: AnimatedVisibility with scaleIn using Bouncy spring
  • Close button: animateFloatAsState on rotation + fadeIn

After link loads, card slides up from below + fades in. While loading: shimmer placeholder.

  • Compose API: AnimatedVisibility with slideInVertically + fadeIn using Gentle spring
  • Shimmer: infinite rememberInfiniteTransition on a translucent gradient offset

10. Schedule chip (SPRING/Bouncy)

After picking date, chip pops in with bouncy spring. Clock icon subtly rotates.

  • Compose API: AnimatedVisibility with scaleIn using Bouncy
  • Icon: animateFloatAsState rotation 0→360

11. Publish button (BOUNCE/Bouncy)

Subtle bounce on activation. During publishing: loading pulse (alpha animation). On success: checkmark icon scales in.

  • Compose API: Scale bounce on click via animateFloatAsState; rememberInfiniteTransition for pulse; AnimatedContent for icon swap with scaleIn

12. Character counter color (FADE/Quick)

Smooth color crossfade when exceeding 280 characters — neutral → red.

  • Compose API: animateColorAsState with Quick tween
  • Trigger: text.length > 280

13. Action buttons staggered entrance (SCALE/Gentle)

Draft, Schedule, Publish buttons — cascading scale-in with 50ms delay each.

  • Compose API: AnimatedVisibility per button with scaleIn + StaggerDelay * index

Detail Screen — 4 animations

14. Content reveal (FADE/Gentle)

Elements appear sequentially: status badge → text → image → metadata. 80ms delay each.

  • Compose API: AnimatedVisibility per section with fadeIn + slideInVertically(initialOffsetY = { 20 })
  • Delay: LaunchedEffect with delay(80 * index)

15. Status badge entrance (SPRING/Bouncy)

Badge scales from 0 with bounce — first visible element, draws attention.

  • Compose API: animateFloatAsState scale with Bouncy spring

16. Delete confirmation dialog (SCALE/Snappy)

Dialog scales from center (0.8→1.0) with spring + backdrop fades in.

  • Compose API: Custom dialog wrapper with scaleIn(initialScale = 0.8f) + fadeIn backdrop
  • Spring spec: Snappy

17. Metadata section (SLIDE/Gentle)

Bottom metadata slides up — last in the reveal sequence.

  • Compose API: AnimatedVisibility with slideInVertically + Gentle spring

Settings Screen — 2 animations

18. "Saved!" feedback (SPRING/Bouncy)

Green "Saved!" text pops in with bounce (scale 0→1). After 2 seconds, fades out.

  • Compose API: AnimatedVisibility with scaleIn using Bouncy spring; LaunchedEffectdelay(2000) → hide
  • Exit: fadeOut with Quick tween

19. Disconnect dialog (FADE+SCALE/Snappy)

Same pattern as delete dialog — scale from center + backdrop fade. "Disconnect" button has subtle red pulse.

  • Compose API: Same custom dialog wrapper as #16
  • Red pulse: rememberInfiniteTransition on alpha of error color

Navigation Transitions — 5

Feed → Composer

Slide up from bottom + fade. Conceptually: FAB transforms into full screen.

  • enterTransition = slideInVertically(initialOffsetY = { it }) + fadeIn()
  • exitTransition = slideOutVertically(targetOffsetY = { it }) + fadeOut()

Feed → Detail

Slide in from right + fade. Post card expands into full view.

  • enterTransition = slideInHorizontally(initialOffsetX = { it }) + fadeIn()
  • exitTransition = slideOutHorizontally(targetOffsetX = { it }) + fadeOut()

Feed → Settings

Standard slide from right.

  • enterTransition = slideInHorizontally(initialOffsetX = { it })
  • exitTransition = slideOutHorizontally(targetOffsetX = { it })

Back (all screens)

Reverse of the entry animation for each screen.

Setup → Feed

Crossfade — smooth transition from animated setup background to feed.

  • enterTransition = fadeIn(tween(500))
  • exitTransition = fadeOut(tween(500))

File Structure

ui/
├── animation/
│   └── SwooshMotion.kt          # Shared animation specs object
├── components/
│   └── AnimatedDialog.kt        # Reusable animated dialog wrapper (used by #16, #19)
├── feed/
│   └── FeedScreen.kt            # Modified: #1-#7
├── composer/
│   └── ComposerScreen.kt        # Modified: #8-#13
├── detail/
│   └── DetailScreen.kt          # Modified: #14-#17
├── settings/
│   └── SettingsScreen.kt        # Modified: #18-#19
└── navigation/
    └── NavGraph.kt              # Modified: navigation transitions

New files: 2

  • ui/animation/SwooshMotion.kt
  • ui/components/AnimatedDialog.kt

Modified files: 5

  • FeedScreen.kt, ComposerScreen.kt, DetailScreen.kt, SettingsScreen.kt, NavGraph.kt

Testing Strategy

  • Existing unit tests should pass unchanged (animations don't affect business logic)
  • Manual verification: each animation visually correct on emulator
  • Compose Preview for individual animated components where feasible