# Apple Motion Feel

> >-

- **Type:** Skill
- **Install:** `agentstack add skill-yordilorenzo-liquid-glass-skills-apple-motion-feel`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [YordiLorenzo](https://agentstack.voostack.com/s/yordilorenzo)
- **Installs:** 0
- **Category:** [Agent Skills](https://agentstack.voostack.com/c/agent-skills)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [YordiLorenzo](https://github.com/YordiLorenzo)
- **Source:** https://github.com/YordiLorenzo/liquid-glass-skills/tree/main/apple-motion-feel

## Install

```sh
agentstack add skill-yordilorenzo-liquid-glass-skills-apple-motion-feel
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# Apple motion feel

Most "this animation feels off" bugs are not timing bugs. They are one of four category errors:
bounce on something nothing threw, motion that ignores the gesture that caused it, an animation
that restarts from the wrong value when interrupted, or feedback that arrives after the user
already let go. Fix the category first; only then argue about the number.

## The one table that decides damping

**Bounce is earned by momentum.** If the user threw it, it overshoots. If the user tapped it, it
does not. This single rule resolves most spring arguments.

| Interaction | Damping | Response |
|---|---|---|
| Tap-driven — buttons, toggles, selection, panels opened by a button | **1.0** (critically damped) | 0.3–0.4s |
| Momentum-driven — flicks, throws, drag release | **0.8** | 0.3–0.4s |
| Move / reposition | 1.0 | 0.4s |
| Rotation | 0.8 | 0.4s |
| Drawer / sheet | 0.8 | 0.3s |

A tap-opened drawer takes damping **1.0**, not the 0.8 in the drawer row — the row assumes the
drag-to-open drawer it was measured on. Read the trigger, not the component name.

Symptom mapping: **wobble on a tap** means damping too low, not duration too long. **Sluggish** is
usually response too high, not damping. **Cheap/mechanical** is usually a linear or ease-in-out
curve where a spring belongs.

## SwiftUI's presets, in the terms above

| Preset | Damping | Use |
|---|---|---|
| `.smooth` | critically damped, **no overshoot** | the default for tap-driven UI |
| `.snappy` | slightly underdamped, small overshoot | brisk feedback, small elements |
| `.bouncy` | low damping, visible oscillation | playful, deliberate character |

All three default to `duration: 0.5`; all take `duration:` and `extraBounce:`. Reach for a preset
before a hand-rolled `.spring(response:dampingFraction:)` — a named preset states intent, and
`0.34/0.82` states nothing.

With the modern `.spring(duration:bounce:)` form, `bounce` is the readable axis:

| bounce | Reads as |
|---|---|
| `0.0` | critically damped, smooth arrival |
| `0.15` | brisk, not bouncy |
| `0.2` | drag release with velocity |
| `0.3` | exaggerated, deliberate |
| `> 0.4` | extreme — wrong for almost all UI |
| ` Double {
    (initialVelocity / 1000) * decelerationRate / (1 - decelerationRate)
}
```

**Rubber-band at boundaries, never hard-stop.** Resistance should grow progressively past the edge.

## Interruptibility

**Never start an animation from its target value.** Read the live on-screen presentation value, or
an interrupted animation visibly jumps. SwiftUI springs animate from the current value by default —
which is precisely why hand-rolling a morph with keyframes or timers is worse than it looks.

**Never lock out input while animating.** `.allowsHitTesting(false)` across an animating region
needs a stated reason. Springs retarget for free; a user who changes their mind mid-animation
should be obeyed, not queued.

**One animation source per property.** Never mix implicit `.animation`, `withAnimation`, and
gesture-driven updates on the same value — an implicit modifier later in the view tree silently
wins over an explicit `withAnimation`, and the resulting fight is invisible in code review.

**The animation context lives outside the conditional.** An `.animation()` written inside an `if`
is destroyed along with the view, so removal never animates. Put it on a view that survives the
change, or use `withAnimation` at the mutation site.

## Ordering

Animate in this order of cost and reliability: **transforms** (scale, offset, rotation) first,
**frames** second, **identity changes** last. A transform is cheap and interpolates exactly; a
frame change forces layout; an identity change cannot interpolate at all and gives you a cut.

Corollary: a "morph" between two mutually exclusive views is an identity change and will not
interpolate. Morph one persistent view whose properties animate, rather than swapping two.

## Consistency beats correctness

Three selection controls that each animate at a different speed read as sloppy even when every
individual value is defensible. **Name the animation once in a design system and reference it**;
a bare `withAnimation { }` means nobody chose, and it will not match the sibling that did.

If you find the same interaction animating three ways, the finding is the inconsistency — fix it by
adopting whichever value was deliberately authored, not by inventing a fourth.

## Accessibility is part of the feel

Every app-authored animation needs a Reduce Motion branch — the system tones down its own motion,
never yours. Substitute, do not remove: a state change with no feedback is harder to follow.
Crossfade → shortened (0.1–0.15s, no bounce) → instant, in that order of preference. Gesture
tracking stays; only the release spring is tamed. Full treatment, including glass specifics, is in
the **liquid-glass-motion** skill's `reduce-motion.md`.

## Verify

```bash
# Magic numbers that state no intent — candidates for a named preset
grep -rn "\.spring(response:\|dampingFraction:" --include="*.swift" .

# "Nobody chose" — bare withAnimation next to siblings that did choose
grep -rn "withAnimation {" --include="*.swift" .

# Animation contexts trapped inside conditionals
grep -rn -B3 "\.animation(" --include="*.swift" . | grep -A3 "if "
```

## Sources

The damping/response table, the velocity-handoff formula, the momentum-projection function, and
the gesture do-nots are adapted from [emilkowalski/skills — `apple-design`](https://github.com/emilkowalski/skills/blob/main/skills/apple-design/SKILL.md).
The `bounce` bands are from [GetStream/swiftui-spring-animations](https://github.com/GetStream/swiftui-spring-animations).
Interruptibility and preset semantics are checked against Apple's WWDC23 sessions
[Animate with springs](https://developer.apple.com/videos/play/wwdc2023/10158/) and
[Wind your way through advanced animations in SwiftUI](https://developer.apple.com/videos/play/wwdc2023/10157/).

## Checklist

- [ ] Damping follows the trigger: 1.0 for tap, 0.8 for momentum — not the component's name.
- [ ] Response sits in 0.3–0.4s unless there is a reason on the line.
- [ ] A named preset (`.smooth` / `.snappy` / `.bouncy`) is used where it states the intent.
- [ ] `bounce > 0.4` appears nowhere.
- [ ] Gestures respond on pointer-down and track 1:1; release velocity is handed to the spring.
- [ ] Boundaries rubber-band; nothing hard-stops.
- [ ] No animation starts from its target value; nothing locks out input while animating.
- [ ] One animation source per property; the context lives outside any conditional.
- [ ] The same interaction animates the same way everywhere, from one named constant.
- [ ] Every authored animation has a Reduce Motion branch that substitutes rather than removes.

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [YordiLorenzo](https://github.com/YordiLorenzo)
- **Source:** [YordiLorenzo/liquid-glass-skills](https://github.com/YordiLorenzo/liquid-glass-skills)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-yordilorenzo-liquid-glass-skills-apple-motion-feel
- Seller: https://agentstack.voostack.com/s/yordilorenzo
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
