# Animation Study & Motion Design Language

*Updated: 2026-04-05*

## Current State

The codebase has a consistent but minimal animation foundation:

| Duration | Easing | Used For |
|----------|--------|----------|
| 0.2s | ease | Interactive feedback (buttons, inputs, selections, toggles) |
| 0.3s | ease | Progress steps, sparkline bars |
| 0.6s | ease | Gauge fills (health bar, social battery) |
| 4.0s | ease-in-out | warmPulse glow (only keyframe animation in codebase) |

**What exists:** Hover/focus transitions on all interactive atoms. Consistent timing.
**What's missing:** Everything else — no entrance animations, no screen transitions, no exit animations, no orchestration, no reduced-motion support, no micro-interactions.

---

## Motion Principles for Angels

### 1. Calm Motion
Mental health apps must not overstimulate. Motion should feel like breathing — gentle, rhythmic, predictable. No bouncing, no shaking, no attention-grabbing pulses (except crisis alerts).

### 2. Purposeful Direction
Every animation communicates spatial relationship:
- **Forward in a journey** → content slides LEFT (new content enters from right)
- **Back in a journey** → content slides RIGHT (previous content returns)
- **Deeper into detail** → content rises UP (drill-down)
- **Returning to overview** → content sinks DOWN (zoom out)
- **Confirming/completing** → gentle scale + fade (satisfaction)

### 3. Progressive Revelation
Multi-step forms and lists stagger their children. The user's eye follows a natural reading path. Nothing appears all at once — elements enter in logical order.

### 4. Respect User Preferences
All motion respects `prefers-reduced-motion`. In reduced-motion mode, transitions become instant opacity changes — layout shifts still happen, but over 0ms.

---

## Motion Tokens

Add to `stories/tokens.css`:

```css
:root {
  /* Durations */
  --duration-instant: 0ms;
  --duration-micro: 100ms;      /* button press, toggle */
  --duration-fast: 200ms;       /* hover, focus, selection */
  --duration-normal: 300ms;     /* screen transitions, reveals */
  --duration-slow: 500ms;       /* gauge fills, celebrations */
  --duration-dramatic: 800ms;   /* crisis mode, major state changes */

  /* Easings */
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);        /* decelerate — entering */
  --ease-in: cubic-bezier(0.55, 0, 1, 0.45);         /* accelerate — exiting */
  --ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);     /* symmetric — looping */
  --ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);  /* overshoot — celebration */
  --ease-gentle: cubic-bezier(0.4, 0, 0.2, 1);       /* Material-style — default */

  /* Stagger */
  --stagger-child: 50ms;       /* delay between list items */
  --stagger-section: 100ms;    /* delay between page sections */

  /* Distances */
  --slide-distance: 24px;      /* how far elements travel on enter/exit */
  --scale-press: 0.97;         /* button press scale */
  --scale-enter: 0.95;         /* modal/card entrance scale */
}

@media (prefers-reduced-motion: reduce) {
  :root {
    --duration-instant: 0ms;
    --duration-micro: 0ms;
    --duration-fast: 0ms;
    --duration-normal: 0ms;
    --duration-slow: 0ms;
    --duration-dramatic: 0ms;
    --stagger-child: 0ms;
    --stagger-section: 0ms;
    --slide-distance: 0px;
    --scale-press: 1;
    --scale-enter: 1;
  }
}
```

---

## Animation Categories

### A. Micro-Interactions (enhance existing atoms)

**Button Press**
```css
.btn:active {
  transform: scale(var(--scale-press));
  transition: transform var(--duration-micro) var(--ease-out);
}
```

**Toggle Flip** — knob slides with spring overshoot
```css
.toggle-knob {
  transition: transform var(--duration-fast) var(--ease-spring);
}
```

**Chip Selection** — subtle scale + colour shift
```css
.chip--selected {
  transform: scale(1.05);
  transition: all var(--duration-fast) var(--ease-out);
}
```

**Input Focus** — border glow expands outward
```css
.input:focus {
  box-shadow: 0 0 0 3px var(--warm-amber-light);
  transition: box-shadow var(--duration-fast) var(--ease-out);
}
```

### B. Screen Transitions (journey navigation)

**Journey Forward** — current screen slides out left, new slides in from right
```css
@keyframes screenEnterForward {
  from {
    opacity: 0;
    transform: translateX(var(--slide-distance));
  }
  to {
    opacity: 1;
    transform: translateX(0);
  }
}

@keyframes screenExitForward {
  from {
    opacity: 1;
    transform: translateX(0);
  }
  to {
    opacity: 0;
    transform: translateX(calc(-1 * var(--slide-distance)));
  }
}

.screen-enter-forward {
  animation: screenEnterForward var(--duration-normal) var(--ease-out) forwards;
}

.screen-exit-forward {
  animation: screenExitForward var(--duration-normal) var(--ease-in) forwards;
}
```

**Journey Back** — reverse of forward (slide right)
```css
@keyframes screenEnterBack {
  from { opacity: 0; transform: translateX(calc(-1 * var(--slide-distance))); }
  to { opacity: 1; transform: translateX(0); }
}

@keyframes screenExitBack {
  from { opacity: 1; transform: translateX(0); }
  to { opacity: 0; transform: translateX(var(--slide-distance)); }
}
```

**Modal/Detail Enter** — scale up from center + fade
```css
@keyframes modalEnter {
  from { opacity: 0; transform: scale(var(--scale-enter)); }
  to { opacity: 1; transform: scale(1); }
}
```

### C. Content Choreography (staggered reveals)

**List Items** — stagger children on screen enter
```css
.stagger-children > * {
  opacity: 0;
  transform: translateY(calc(var(--slide-distance) / 2));
  animation: fadeSlideUp var(--duration-normal) var(--ease-out) forwards;
}

.stagger-children > *:nth-child(1) { animation-delay: 0ms; }
.stagger-children > *:nth-child(2) { animation-delay: var(--stagger-child); }
.stagger-children > *:nth-child(3) { animation-delay: calc(var(--stagger-child) * 2); }
.stagger-children > *:nth-child(4) { animation-delay: calc(var(--stagger-child) * 3); }
.stagger-children > *:nth-child(5) { animation-delay: calc(var(--stagger-child) * 4); }
/* ... up to 10 */

@keyframes fadeSlideUp {
  to { opacity: 1; transform: translateY(0); }
}
```

**Progress Steps** — dots fill sequentially
```css
.progress-step--completed {
  animation: stepComplete var(--duration-fast) var(--ease-spring) forwards;
}

@keyframes stepComplete {
  0% { transform: scale(1); background: var(--border); }
  50% { transform: scale(1.3); }
  100% { transform: scale(1); background: var(--warm-amber); }
}
```

### D. Emotional State Animations

**Crisis Mode Enter** — screen darkens, pulse begins
```css
@keyframes crisisEnter {
  0% { background: var(--surface); }
  100% { background: var(--crisis-navy); }
}

@keyframes crisisPulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(232, 184, 109, 0.4); }
  50% { box-shadow: 0 0 0 12px rgba(232, 184, 109, 0); }
}

.crisis-active {
  animation:
    crisisEnter var(--duration-dramatic) var(--ease-gentle) forwards,
    crisisPulse 2s var(--ease-in-out) 0.8s infinite;
}
```

**Streak Celebration** — number counts up + confetti burst
```css
@keyframes countUp {
  from { opacity: 0; transform: translateY(20px) scale(0.8); }
  to { opacity: 1; transform: translateY(0) scale(1); }
}

@keyframes celebrationBurst {
  0% { transform: scale(0); opacity: 1; }
  50% { transform: scale(1.2); opacity: 0.8; }
  100% { transform: scale(1.5); opacity: 0; }
}

.streak-number {
  animation: countUp var(--duration-slow) var(--ease-spring) forwards;
}
```

**Mood Selection** — selected mood gently pulses
```css
@keyframes moodSelected {
  0% { transform: scale(1); }
  50% { transform: scale(1.08); }
  100% { transform: scale(1.05); }
}
```

**Safety — Calm Breathing** — used during emotional readiness check
```css
@keyframes breathe {
  0%, 100% { transform: scale(1); opacity: 0.6; }
  50% { transform: scale(1.1); opacity: 1; }
}

.breathing-circle {
  animation: breathe 4s var(--ease-in-out) infinite;
}
```

### E. Loading & Skeleton States

**Skeleton Shimmer**
```css
@keyframes shimmer {
  0% { background-position: -200% 0; }
  100% { background-position: 200% 0; }
}

.skeleton {
  background: linear-gradient(
    90deg,
    var(--border) 25%,
    var(--warm-amber-light) 50%,
    var(--border) 75%
  );
  background-size: 200% 100%;
  animation: shimmer 1.5s var(--ease-in-out) infinite;
  border-radius: var(--radius-sm);
}
```

---

## Implementation Priority

### Phase 1: Tokens + Micro-interactions (Day 1)
1. Add motion tokens to `stories/tokens.css`
2. Update Button with press feedback
3. Add `prefers-reduced-motion` media query
4. Update Toggle with spring easing

### Phase 2: Screen Transitions (Day 2-3)
5. Build journey player transition system (forward/back/modal)
6. Add stagger-children utility class
7. Implement PhoneFrame content transition wrapper

### Phase 3: Emotional Animations (Day 4-5)
8. Crisis mode entrance + pulse
9. Streak celebration
10. Mood selection feedback
11. Breathing circle for readiness check

### Phase 4: Polish (Day 6-7)
12. Skeleton loading states
13. Progress step animations
14. Nudge card entrance animations
15. Notification toast animations

---

## Testing Motion

Each animation should be testable in Storybook:
- **Static**: default render (no animation playing)
- **Entering**: animation playing on mount
- **Interactive**: triggered by user action (hover, click, selection)
- **Reduced Motion**: `prefers-reduced-motion` emulated

Storybook's `globals` panel can toggle reduced-motion for all stories simultaneously.
