# PR #323 — motion-polish follow-up

**Date:** 2026-05-17
**State at merge:** `65c813b7` (after diagnostic strip + rebase onto `origin/main`).
**Ships:** dual-`Animated.Value` glow motion with safe translate-tied-to-scale + escalating per-boundary durations + direction-aware cubic easings (`Easing.out(Easing.cubic)` forward / `Easing.in(Easing.cubic)` back) + Animated.View transform wrapper + scale-first transform order. Better than pre-#323 (which had no motion at all) but the **direction-asymmetry feel and the OUT bounce both need another iteration**.

## What lands on main

The motion code in `src/shared/ui/AuthScreenBackground.tsx` after merge:

- Two independent native-driver timings — `outProgress` (drives the outgoing layer 0→1) + `inProgress` (drives the incoming layer 0→1). Staggered via plain `setTimeout(() => inAnim.start(), boundary.duration / 2)`. Avoids `Animated.delay`/`Animated.sequence`/`Animated.parallel` because `Animated.delay` hard-codes `useNativeDriver: false` and silently downgrades the whole composition.
- Per-boundary motion config (Q1↔Q2, Q2↔Q3, Q3↔next) with (x, y) translate vectors at ±30 px and escalating durations 400/450/500 ms.
- OUT layer: multi-stop bounce on scale `[0, 0.7, 1] → [1.0, 1.15, 1.10]` and on translate `[0, 0.7, 1] → [0, vec, 0]`. Opacity `0.8 → 0`.
- IN layer: over-scaled start `[0, 1] → [1.20, 1.00]`, translate `[0, 1] → [vec, 0]` linear (tied to scale so the PNG edge never exposes). Opacity `0 → 0.8`.
- Transform order `[scale, translateX, translateY]` — scale-first to work around the iOS native-driver bug where scale silently drops when translates precede it.
- Direction-aware cubic easings; X-vector mirrored on backward navigation, Y kept.

## Known gaps the user flagged but accepted as "merge now, polish later"

1. **Direction asymmetry could be stronger.** Forward (decelerate-into-place) and backward (accelerate-retreat) share the same scale + translate values; only the easing flips. A polish iteration could:
   - Use distinct scale ranges per direction (e.g. IN forward `1.20 → 1.00`, IN backward `0.85 → 1.00` — undersized arrival).
   - Different per-direction translate magnitude.
   - Different per-direction bounce peak placement on OUT.

2. **Bounce feel may be too subtle on the OUT layer.** Current peak at `outProgress = 0.7` with scale 1.15 and settle at 1.10. Polish ideas:
   - Move the peak earlier (`0.5` or `0.6`) so the bounce feels snappier.
   - Use `Animated.spring` instead of `Animated.timing` with a tuned damping/stiffness for a physical-feel bounce.
   - Add a slight rotation kick at the peak (`rotateZ ±3deg`) to make the bounce read as more energetic.

3. **Per-step personality is location-only.** Vectors (UL / UR / LR) give each boundary a different drift direction but the SHAPE of the motion is identical. Could vary:
   - Different durations per boundary (already done — 400/450/500).
   - Different bounce peaks per boundary (Q3 has a deeper bounce?).
   - Different over-scale magnitudes per boundary.

4. **Diagnostic logs from `/tmp/diag-323.log`** captured the verified `outProgress` + `inProgress` 0→1 ramps and `completed=true` finishes after the cancellation fix (`540c27a7`). Reference for next iteration if motion needs more debugging.

## Files to revisit for polish

- `src/shared/ui/AuthScreenBackground.tsx` — all motion config (`BOUNCE_CONFIGS`, `buildOutMotion`, `buildInMotion`, `startTransition`, `EASING_FORWARD` / `EASING_BACKWARD`).
- `src/features/onboarding/presentation/screens/QuestionScreen.tsx` — `step` plumbed already.
- `src/features/onboarding/presentation/routes/QuestionnaireRoute.tsx` — `prevStepRef` + `direction` derivation already wired.

## Suggested next steps for the polish iteration

1. Pick which gap to tackle first (1 / 2 / 3 above).
2. Spawn a fresh agent with this doc as starting context.
3. Run dev server on `8086` again (per the pattern this PR established), grep `/tmp/diag-X.log` if re-instrumenting.
4. Don't bundle the polish into another umbrella PR — single-purpose PR per gap keeps review tight.

## What NOT to redo

- ✗ The cancellation fix (`540c27a7`) — keep as-is. Deps array shape is correct.
- ✗ The mid-mount race fix (`22ab874c`) — front layer must stay mounted; do not gate on transition state.
- ✗ The Animated.delay → setTimeout swap (`a288f41e`) — composition pattern that ships works. Don't revert to Animated.parallel/sequence with delay inside.
- ✗ The transform array order — `[scale, translateX, translateY]` is the version that works on this RN.
- ✗ Diagnostic logs — strip-as-you-go pattern is fine, but don't ship them.

End of follow-up notes.
