@ericrisco/rsc 1.1.5 → 1.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README.md +11 -11
  2. package/manifest.json +56 -231
  3. package/package.json +1 -1
  4. package/scripts/lib/domains.js +3 -3
  5. package/skills/design/SKILL.md +3 -3
  6. package/skills/design/references/design-systems.md +1 -1
  7. package/skills/design/references/inspiration-sources.md +2 -2
  8. package/skills/design/references/motion-and-interaction.md +2 -2
  9. package/skills/design-dna/evals/cases.yaml +1 -1
  10. package/skills/design-loop/SKILL.md +3 -3
  11. package/skills/design-loop/evals/cases.yaml +3 -3
  12. package/skills/expo/SKILL.md +1 -1
  13. package/skills/motion-craft/SKILL.md +125 -0
  14. package/skills/motion-craft/evals/cases.yaml +38 -0
  15. package/skills/motion-craft/scripts/verify.sh +75 -0
  16. package/skills/react/SKILL.md +2 -2
  17. package/skills/react-native/SKILL.md +1 -1
  18. package/skills/swift-ios/SKILL.md +2 -2
  19. package/skills/ui-engineering/SKILL.md +114 -0
  20. package/skills/ui-engineering/evals/cases.yaml +38 -0
  21. package/skills/variant-explorer/SKILL.md +93 -0
  22. package/skills/variant-explorer/evals/cases.yaml +38 -0
  23. package/skills/animate/SKILL.md +0 -851
  24. package/skills/animate/evals/cases.yaml +0 -36
  25. package/skills/animate/references/recipes.md +0 -324
  26. package/skills/animate-expo/SKILL.md +0 -281
  27. package/skills/animate-expo/evals/cases.yaml +0 -36
  28. package/skills/animate-expo/references/recipes.md +0 -385
  29. package/skills/animation-vocabulary/SKILL.md +0 -189
  30. package/skills/animation-vocabulary/evals/cases.yaml +0 -31
  31. package/skills/apple-design/SKILL.md +0 -301
  32. package/skills/apple-design/evals/cases.yaml +0 -33
  33. package/skills/ask-sonner/SKILL.md +0 -104
  34. package/skills/ask-sonner/evals/cases.yaml +0 -33
  35. package/skills/ask-sonner/references/api.md +0 -64
  36. package/skills/design-eng/SKILL.md +0 -88
  37. package/skills/design-eng/evals/cases.yaml +0 -36
  38. package/skills/find-animation-opportunities/SKILL.md +0 -150
  39. package/skills/find-animation-opportunities/evals/cases.yaml +0 -31
  40. package/skills/improve-animations/SKILL.md +0 -126
  41. package/skills/improve-animations/evals/cases.yaml +0 -31
  42. package/skills/improve-animations/references/audit.md +0 -115
  43. package/skills/improve-animations/references/plan-template.md +0 -73
  44. package/skills/pick-ui-library/SKILL.md +0 -101
  45. package/skills/pick-ui-library/evals/cases.yaml +0 -33
  46. package/skills/pick-ui-library/references/component-galleries.md +0 -59
  47. package/skills/prototype/SKILL.md +0 -113
  48. package/skills/prototype/evals/cases.yaml +0 -31
  49. package/skills/prototype/references/picker.md +0 -197
  50. package/skills/review-animations/SKILL.md +0 -188
  51. package/skills/review-animations/evals/cases.yaml +0 -34
  52. package/skills/review-animations/references/standards.md +0 -187
  53. package/skills/review-animations/scripts/verify.sh +0 -164
  54. package/skills/write-swift/SKILL.md +0 -413
  55. package/skills/write-swift/evals/cases.yaml +0 -36
@@ -1,301 +0,0 @@
1
- ---
2
- name: apple-design
3
- description: "Apple's fluid-interface craft on the web: spring damping and response, velocity handoff, momentum projection on a flick, rubber-banding at a boundary, interruptible motion the user can grab mid-flight, translucent materials and backdrop blur, letter-spacing and leading by size. Use for iOS-grade feel, sheets and drag."
4
- tags: [design, motion, springs, gestures, apple]
5
- recommends: [animate, design, swift-ios]
6
- profiles: [full]
7
- origin: emilkowalski (MIT, adapted)
8
- ---
9
-
10
- # Apple Design
11
-
12
- **Hand-offs.** Curves, durations and the build itself are `../animate/SKILL.md`'s — this file owns
13
- the **physics and materials**, not the CSS token table. The visual system and brand grounding are
14
- `../design/SKILL.md`'s. Native Swift work is `../swift-ios/SKILL.md`'s (platform) and
15
- `../write-swift/SKILL.md`'s (language); everything here is the *web* translation of Apple's talks.
16
-
17
- How Apple builds interfaces that stop feeling like a computer and start feeling like an extension of you. This knowledge comes from Apple's WWDC design talks — chiefly *Designing Fluid Interfaces* (WWDC 2018) — distilled and translated into the web platform (CSS, Pointer Events, `requestAnimationFrame`, spring libraries like Motion/Framer Motion).
18
-
19
- The through-line: **an interface feels alive when motion starts from the current on-screen value, inherits the user's velocity, projects momentum forward, and can be grabbed and reversed at any instant.** Springs are the tool that makes all of this natural, because they are inherently interruptible and velocity-aware.
20
-
21
- ## The Core Idea
22
-
23
- > "When we align the interface to the way we think and move, something magical happens — it stops feeling like a computer and starts feeling like a seamless extension of us."
24
-
25
- An interface is fluid when it behaves like the physical world: things respond instantly, move continuously, carry momentum, resist at boundaries, and can be redirected mid-motion. Everything below is a way to get closer to that.
26
-
27
- Apple frames design as serving four human needs: **safety/predictability, understanding, achievement, and joy.** Every rule here serves one of them.
28
-
29
- ## 1. Response — kill latency
30
-
31
- The moment lag appears, the feeling of directness "falls off a cliff." Response is the foundation everything else is built on.
32
-
33
- - **Respond on pointer-down, not on release.** Highlight a button the instant it's pressed. Waiting for `click`/touch-up to show feedback feels dead.
34
- - **Be vigilant about every latency.** Audit debounces, artificial timers, transition waits, and the ~300ms tap delay. Anything on the input path that isn't essential is a regression.
35
- - **Feedback must be continuous *during* the interaction, not just at the end.** For a drag, slider, or drawer, update the UI 1:1 with the pointer the whole way through — never animate only when the gesture completes.
36
-
37
- ```css
38
- /* Feedback lives on the press, and it's instant */
39
- .button:active {
40
- transform: scale(0.97);
41
- transition: transform 100ms ease-out;
42
- }
43
- ```
44
-
45
- ## 2. Direct manipulation — 1:1 tracking
46
-
47
- > "Touch and content should move together."
48
-
49
- When the user drags something, it must stay glued to the finger — and respect the offset from *where they grabbed it*. Snapping to the element's center on grab breaks the illusion immediately.
50
-
51
- - Use Pointer Events with `setPointerCapture` so tracking continues even when the pointer leaves the element's bounds.
52
- - Track a short **velocity/position history** (last few `pointermove` events), not just the current point — you'll need velocity at release.
53
-
54
- ```js
55
- el.addEventListener('pointerdown', (e) => {
56
- el.setPointerCapture(e.pointerId);
57
- const grabOffset = e.clientY - el.getBoundingClientRect().top; // respect where they grabbed
58
- // ...track position + timestamp history for velocity
59
- });
60
- ```
61
-
62
- ## 3. Interruptibility — the single most important principle
63
-
64
- > "The thought and the gesture happen in parallel."
65
-
66
- Every animation must be interruptible and redirectable at any moment. A user must be able to grab a moving element mid-flight and reverse it without waiting for the animation to finish. A closing modal the user grabs again should follow the finger — not finish closing first, then reopen.
67
-
68
- - **Never lock out input during a transition.**
69
- - **Always animate from the *presentation* (current) value, never the target value.** On interrupt, read the element's live on-screen transform and start the new animation from there. Starting from the logical/target value causes a visible jump.
70
- - **Avoid CSS transitions and `@keyframes` for anything gesture-driven** — they can't be smoothly grabbed and reversed mid-flight. Springs animate from the current value by default, which is exactly what interruption needs.
71
- - **When a gesture reverses, blend velocity — don't hard-cut it.** Replacing one animation with another at a reversal creates a velocity discontinuity, a "brick wall." Spring libraries that carry velocity through a re-target avoid it. (This is what iOS's *additive animations* do natively; on the web, choose a spring library that re-targets from the current velocity.)
72
- - **Decompose 2D motion into independent X and Y springs.** A single spring on a 2D distance desyncs when X and Y have different velocities.
73
-
74
- ## 4. Behavior over animation — use springs
75
-
76
- > "Think of animation as a conversation between you and the object, not something prescribed by the interface."
77
-
78
- A pre-scripted, fixed-duration animation can't respond to new input. A spring can — new input just changes the target, and the motion stays continuous. Reach for springs for anything a user can touch.
79
-
80
- Apple deliberately replaced the physics triplet (mass/stiffness/damping) with two designer-friendly parameters. Think in these:
81
-
82
- - **Damping ratio** — controls overshoot. `1.0` = critically damped, no bounce, smooth settle. `< 1.0` = overshoots and oscillates. Lower = bouncier.
83
- - **Response** — how quickly the value reaches the target, in seconds. Lower = snappier. **This is not "duration"** — a spring has no fixed duration; its settle time emerges from the parameters.
84
-
85
- **Defaults:**
86
- - Start most UI at **damping `1.0`** (critically damped) — graceful and non-distracting.
87
- - Add bounce (**damping ~`0.8`**) **only when the gesture itself carried momentum** (a flick, a throw, a drag release). Overshoot on a menu that just faded in feels wrong; overshoot on a card you flicked feels right.
88
-
89
- **Concrete values Apple ships:**
90
-
91
- | Interaction | Damping | Response |
92
- | --- | --- | --- |
93
- | Move / reposition (e.g. PiP) | `1.0` | `0.4` |
94
- | Rotation | `0.8` | `0.4` |
95
- | Drawer / sheet | `0.8` | `0.3` |
96
-
97
- **Web mapping (Motion / Framer Motion):** the `bounce` + `duration` spring API maps closely to Apple's damping + response. A safe house style is `damping: 1.0` springs everywhere by default; reserve bounce for momentum-driven, physical interactions.
98
-
99
- ```js
100
- import { animate } from 'motion';
101
-
102
- // Critically damped default (no overshoot)
103
- animate(el, { y: 0 }, { type: 'spring', bounce: 0, duration: 0.4 });
104
-
105
- // Momentum interaction — a little bounce, only because a flick preceded it
106
- animate(el, { y: target }, { type: 'spring', bounce: 0.2, duration: 0.4 });
107
- ```
108
-
109
- ## 5. Velocity handoff — the seam between drag and animation
110
-
111
- When a gesture ends, the animation must **continue at the finger's exact velocity**, so there's no visible seam between dragging and animating. This is the detail that most separates "fluid" from "fine."
112
-
113
- Pass the pointer's release velocity as the spring's initial velocity. Some spring APIs want **relative** velocity — normalize it by the remaining distance to the target:
114
-
115
- ```
116
- relativeVelocity = gestureVelocity / (targetValue − currentValue)
117
- ```
118
-
119
- Example: element at `y=50`, target `y=150` (100px to go), finger moving 50px/s → initial spring velocity = `50 / 100 = 0.5`. Framer Motion / Motion take absolute px/s velocity directly (`velocity` option), so you usually hand it the raw value.
120
-
121
- ## 6. Momentum projection — animate to where the gesture is *going*
122
-
123
- > "Take a small input and make a big output."
124
-
125
- Don't snap to the nearest boundary from the *release point*. Use velocity to **project the resting position** — exactly like scroll deceleration — then snap to the target nearest that projected point. This is what makes a flick feel like it throws the element.
126
-
127
- Apple's exact projection function (from the *Designing Fluid Interfaces* sample code):
128
-
129
- ```js
130
- // decelerationRate ≈ 0.998 for normal scroll feel; 0.99 for snappier
131
- function project(initialVelocity /* px/s */, decelerationRate = 0.998) {
132
- return (initialVelocity / 1000) * decelerationRate / (1 - decelerationRate);
133
- }
134
-
135
- const projectedEndpoint = currentPosition + project(releaseVelocity);
136
- const target = nearestSnapPoint(projectedEndpoint); // choose target from the projection
137
- animateSpringTo(target, { velocity: releaseVelocity }); // then hand off velocity (§5)
138
- ```
139
-
140
- Note: the physics-textbook `v²/(2·decel)` is *not* what Apple ships — use the exponential-decay form above. This is the standard behavior in good bottom-sheets and carousels (Vaul, Embla).
141
-
142
- ## 7. Spatial consistency — symmetric paths, anchored origins
143
-
144
- > "If something disappears one way, we expect it to emerge from where it came."
145
-
146
- - **Enter and exit along the same path.** A panel that slides in from the right must dismiss to the right. In-from-right / out-the-bottom feels disconnected and confusing.
147
- - **Anchor interactions to their source.** A menu, popover, or sheet should originate from the element that triggered it — set `transform-origin` to the trigger, so the spatial relationship between button and content is obvious. (This is the same origin-awareness point as popovers scaling from their trigger, not their center.)
148
- - **Mirror the easing on reversible transitions** so the outbound path matches the return path (use inverse cubic-bézier control points for the two directions).
149
-
150
- ## 8. Hint in the direction of the gesture
151
-
152
- Humans predict a final state from a trajectory. Intermediate motion should telegraph where things are going — Control Center modules "grow up and out toward your finger." Make the in-between frames point at the outcome, not just interpolate blindly to it.
153
-
154
- ## 9. Rubber-banding — soft boundaries
155
-
156
- At an edge, resist progressively instead of stopping hard. A hard stop reads as "frozen"; continuous resistance reads as "responsive, but there's nothing more here." Apply damping that increases the further past the boundary the user drags.
157
-
158
- ```js
159
- // The further past the bound, the less the element follows — real things slow before they stop
160
- function rubberband(overshoot, dimension, constant = 0.55) {
161
- return (overshoot * dimension * constant) / (dimension + constant * Math.abs(overshoot));
162
- }
163
- ```
164
-
165
- ## 10. Gesture design details (the "feel" checklist)
166
-
167
- - **Tap:** highlight on touch-*down* (instant), commit on touch-*up*. Add ~10px of hysteresis/hit padding around the target, and allow cancel-by-dragging-away and back.
168
- - **Drag/swipe:** require a small movement threshold (hysteresis, ~10px) before committing to a direction, then track 1:1.
169
- - **Detect all plausible gestures in parallel from the first move**, then confidently cancel the losers once intent is clear. Avoid recognizers that only report a *final* state (`swipeleft`-type events) — they throw away the continuous tracking you need for feedback.
170
- - **Minimize disambiguation delays.** Double-tap detection unavoidably delays single taps; only pay that cost where double-tap truly exists.
171
-
172
- ## 11. Frame-level smoothness
173
-
174
- Smoothness is about *what's in the frames*, not just the frame rate.
175
-
176
- - Keep the per-frame positional change below the perception threshold to avoid strobing.
177
- - For very fast motion, a subtle **motion blur / stretch** encodes speed and reads better than a hard sharp streak.
178
- - `requestAnimationFrame` is the web's display-synced clock (Apple uses `CADisplayLink`). Animate only compositor-friendly properties — `transform` and `opacity` — and hint with `will-change` where motion is imminent.
179
-
180
- ## 12. Materials & depth — translucency conveys hierarchy
181
-
182
- Apple uses translucent materials as a floating functional layer that brings structure without stealing focus. On the web, approximate with `backdrop-filter`.
183
-
184
- - **Build nav/toolbars/sheets as translucent layers** (`backdrop-filter: blur()` + a semi-transparent background) with content scrolling underneath — not opaque bars that consume a fixed strip.
185
- - **Material weight encodes hierarchy:** darker/heavier materials separate structural regions (sidebars); lighter materials draw attention to interactive elements (buttons). **Never stack a light translucent surface on another** — legibility collapses.
186
- - **Bigger surfaces should read as thicker:** stronger blur + a deeper shadow than small chips. Consider context-aware shadow — heavier over busy/text content for separation, lighter over plain backgrounds.
187
- - **Dim to focus, separate to keep flow.** A modal task pairs the surface with a dimming scrim and pushes the background back/down. A parallel, non-blocking panel uses translucency and offset *without* a scrim so the flow isn't broken. For stacked sheets, progressively dim and push back each parent layer.
188
- - **Vibrancy keeps text legible over changing backgrounds.** Over blurred/translucent surfaces, don't use flat gray text — use higher-contrast, slightly heavier weight, and a small letter-spacing bump. Put color on a solid layer, not the translucent foreground.
189
- - **Scroll edge effects, not hard dividers.** Instead of a 1px border under a sticky header, fade a small blur/gradient mask where content meets floating chrome — only where floating UI actually overlaps content.
190
- - **Materialize, don't just fade.** For glass/blur surfaces, animate blur radius and scale together on enter/exit, so the surface reads as a real material arriving rather than a plain opacity fade.
191
-
192
- ```css
193
- .toolbar {
194
- background: rgba(255, 255, 255, 0.6);
195
- backdrop-filter: blur(20px) saturate(180%);
196
- border-top: 1px solid rgba(255, 255, 255, 0.4); /* bright top edge = light catching the material */
197
- }
198
- ```
199
-
200
- ## 13. Multimodal feedback — motion + sound + haptics
201
-
202
- Three rules for combining senses (from *Designing Audio-Haptic Experiences*):
203
-
204
- 1. **Causality** — it must be obvious what caused the feedback. Trigger it on the actual causal event (the toggle flipping, the item snapping home), and match its character to the action's physicality.
205
- 2. **Harmony** — the visual, the sound, and the haptic must fire on the **same frame**. Latency between them destroys the illusion. Don't let a CSS transition lag the audio/haptic (Vibration API).
206
- 3. **Utility** — add feedback only where it earns its place. Reserve haptics/sound for meaningful moments (success, error, commit, snap). Over-feedback trains users to ignore all of it.
207
-
208
- ## 14. Reduced motion & accessibility
209
-
210
- Reduced motion doesn't mean *no* feedback — it means a gentler, non-vestibular equivalent. Respond to three independent signals and bake them into your components:
211
-
212
- - **`prefers-reduced-motion: reduce`** — replace slides/springs/parallax with short opacity **cross-fades or static transitions**. Drop elastic/overshoot. Keep opacity/color changes that aid comprehension.
213
- - **`prefers-reduced-transparency: reduce`** — make translucent surfaces frostier/solid: raise background opacity, drop the blur.
214
- - **`prefers-contrast: more`** — near-solid backgrounds with a defined, contrasting border.
215
-
216
- Also: avoid full-viewport moving backgrounds, slow looping oscillations (near 0.2 Hz / one cycle per 5s), and abrupt brightness jumps (ease dark↔light theme changes). Make large moving objects semi-transparent while they travel, and fade big surfaces out during a large reposition and back in once settled.
217
-
218
- ```css
219
- @media (prefers-reduced-motion: reduce) {
220
- .sheet { transition: opacity 200ms ease; transform: none !important; }
221
- }
222
- @media (prefers-reduced-transparency: reduce) {
223
- .toolbar { background: white; backdrop-filter: none; }
224
- }
225
- ```
226
-
227
- ## 15. Typography — optical sizing, tracking, leading
228
-
229
- Apple designs type to change shape with size; the same discipline applies on the web. (From *The Details of UI Typography*, WWDC 2020.)
230
-
231
- - **Tracking (letter-spacing) is size-specific — never one value for all sizes.** Large display text wants *negative* tracking (letters read too far apart as they grow); small text wants slightly *positive* tracking for legibility. A fixed `letter-spacing` is wrong somewhere. Tighten headings, leave body near `0`.
232
- - **Leading (line-height) tracks size inversely.** Tight on large headings, looser on body copy. Increase it for scripts with tall ascenders/descenders; tighten it for dense, information-heavy UI.
233
- - **Build hierarchy from weight + size + leading as a set,** not size alone. Emphasize with weight — it adds presence without taking more space.
234
- - **Respect the user's text-size setting** (Dynamic Type). Scale layout *with* the text — spacing in `rem`/`em`, not fixed px — so a larger font doesn't break the layout.
235
- - **Default to the platform's system font** before a custom face; it already ships optical sizing, tracking tables, and legibility tuning. Override only with a reason.
236
-
237
- ```css
238
- :root { font: 100%/1.5 system-ui, sans-serif; } /* body: system font, comfortable leading */
239
-
240
- .display {
241
- font-size: clamp(2rem, 5vw, 4rem);
242
- line-height: 1.05; /* tight leading for large text */
243
- letter-spacing: -0.02em; /* negative tracking as it grows */
244
- font-optical-sizing: auto;
245
- }
246
- ```
247
-
248
- ## 16. Design foundations — the eight principles
249
-
250
- The motion and craft above serve Apple's eight design principles (*Principles of Great Design*, WWDC 2026). Use these as the names you reason with:
251
-
252
- 1. **Purpose.** Make with intention; decide what *not* to build. Every feature asks for the user's time, attention, and trust — spend that budget only where it pays off.
253
- 2. **Agency.** Keep people in control: offer choices, don't force a single path. Back it with forgiveness — easy undo for slips, a confirmation dialog only for genuinely destructive, irreversible actions (use sparingly; overusing it trains people to click through).
254
- 3. **Responsibility.** Act in the user's interest. Privacy: ask at the right moment, only for what's needed, transparently. Safety: anticipate misuse and harm — especially with AI (an allergy-aware recipe app must not suggest a harmful ingredient). Add previews, confirmations, disclaimers; cut a feature whose risk outweighs its value.
255
- 4. **Familiarity.** Build on what people already know. Use metaphors that are neither too literal nor too abstract (a trash can means delete), and honor their physics. Be consistent: things that look the same must behave the same and live in the same place (close is always top-left on macOS) so people can predict what happens next. Only break a familiar pattern if you can prove it's better — then test it, don't assume.
256
- 5. **Flexibility.** Design for different contexts, devices, and the full range of abilities. Adapt to the platform (iPhone = quick touch; desktop = deep workflows with precise pointer control) and to the situation. Design inclusively (age, language, expertise, accessibility). When no single layout fits everyone, let people personalize — rearrange controls, hide what they don't use.
257
- 6. **Simplicity — not minimalism.** Strip the unnecessary so the core purpose shines; burying everything in one place looks minimal but isn't simple. Be concise (plain language, no jargon, fewer steps) and clear (use hierarchy — order, spacing, contrast — so the most important thing is the most obvious). Every element earns its place; sometimes *adding* context simplifies (a video scrubber that shows time remaining). Show the common path first, advanced options one level deeper.
258
- 7. **Craft.** Uncompromising attention to detail builds trust. Beautiful typography, colors that adapt to light/dark, clear iconography, and responsive animations that give immediate, natural feedback. Nothing is random — every spacing, timing, and alignment value is a deliberate choice you can defend. Jittery scroll, misaligned icons, and layouts that break on rotation read as carelessness. Craft needs iteration and longevity — keep evolving the design as features and hardware change.
259
- 8. **Delight.** The result of getting the other seven right, not confetti tacked on top. Decide the emotion you want people to feel (calm, confident, excited) and reinforce it in every decision.
260
-
261
- Tactical rules that serve these:
262
-
263
- - **Feedback comes in four kinds:** status, completion, warning, error. Confirm meaningful actions, expose ongoing status, warn before problems, validate inline (not on submit).
264
- - **Wayfinding.** Every screen should answer: Where am I? Where can I go? What's there? How do I get out? Never trap the user.
265
- - **Grouping & mapping.** Proximity implies relationship; place a control near what it affects and arrange controls to mirror what they change. If you need a label to explain a control, the mapping is weak.
266
- - **Direct, specific labels beat safe generic ones.** Name nav items for their contents ("Progress", "Library"), not vague umbrellas ("Home"). Specificity creates predictability.
267
-
268
- ## 17. Process
269
-
270
- - **Prototype interactively — an interactive demo is worth "a million static designs."** You discover the interface by building and playing with it; a working prototype also sets a concrete bar that prevents a mediocre final implementation.
271
- - **Design interaction and visuals together.** "You shouldn't be able to tell where one ends and the other begins." Motion is not a layer added after the pixels.
272
- - **Test with real people in real context**, and review motion with fresh eyes — play it in slow motion / frame-by-frame to catch what's invisible at full speed.
273
-
274
- ## Quick Reference
275
-
276
- | Need | Technique | Concrete value |
277
- | --- | --- | --- |
278
- | Default UI spring | Critically damped, no overshoot | `damping 1.0`, `response 0.3–0.4` |
279
- | Momentum / flick spring | Under-damped, slight bounce | `damping ~0.8`, `response 0.3–0.4` |
280
- | Gesture → spring velocity | Hand off release velocity | `gestureVelocity / (target − current)` if normalized |
281
- | Flick landing point | Project momentum | `current + (v/1000)·d/(1−d)`, `d ≈ 0.998` |
282
- | Interrupt cleanly | Start from presentation (live) value | read the on-screen transform |
283
- | Avoid reversal "brick wall" | Carry velocity through re-target | spring that blends velocity |
284
- | Reversible transition | Mirror the easing curve | inverse cubic-bézier |
285
- | Decide reverse vs. commit | Use velocity **sign**, not position | at release |
286
- | 1:1 drag | Pointer Events + capture | respect the grab offset |
287
- | Feedback | On pointer-down, continuous | never only at the end |
288
- | Boundary | Rubber-band, don't hard-stop | progressive resistance |
289
- | Translucent chrome | `backdrop-filter` layer | content scrolls under |
290
- | Type tracking | Size-specific, never fixed | tighten large text (`-0.02em`), body near `0` |
291
- | Reduced motion | Cross-fade, not slide/spring | `@media (prefers-reduced-motion)` |
292
-
293
- ---
294
-
295
- ## Credit
296
-
297
- Adapted for the rsc catalog from **[emilkowalski/skills](https://github.com/emilkowalski/skills)**
298
- (MIT, commit `d23d7f8`) by Emil Kowalski — the design-engineering and animation philosophy behind
299
- [Sonner](https://sonner.emilkowal.ski), [Vaul](https://vaul.emilkowal.ski) and
300
- [animations.dev](https://animations.dev). The craft bar here is his; the routing, gates and evals
301
- that make it an rsc skill are not. Nothing in this body is a substitute for his course.
@@ -1,33 +0,0 @@
1
- skill: apple-design
2
-
3
- should_trigger:
4
- - prompt: "How do I make this drag-to-dismiss sheet feel like iOS — the way it carries momentum when you flick it?"
5
- why: "Velocity handoff and momentum projection, the skill's core subject."
6
- - prompt: "What spring damping and response should I use so this doesn't overshoot?"
7
- why: "Apple's two-parameter spring model with concrete shipped values."
8
- - prompt: "Add rubber-banding so dragging past the edge resists instead of stopping dead."
9
- why: "Named technique with the exact function in the body."
10
- - prompt: "Build a translucent toolbar with a blur that content scrolls underneath, like Apple does."
11
- why: "Translucent materials, material weight and scroll edge effects."
12
- - prompt: "This animation jumps when the user grabs it mid-flight. How do I make it interruptible?"
13
- why: "Interruptibility and animating from the presentation value — called the single most important principle here."
14
- - prompt: "Should letter-spacing change with font size? Our big headings look loose."
15
- why: "Optical sizing and size-specific tracking, an explicit section."
16
-
17
- should_not_trigger:
18
- - prompt: "Which cubic-bezier and duration should this dropdown use?"
19
- route_to: "animate"
20
- why: "Curve and duration tables belong to animate; this skill owns springs, gestures and materials."
21
- - prompt: "Pick our brand palette and the landing page composition."
22
- route_to: "design"
23
- why: "Visual system and brand grounding, not interface physics."
24
- - prompt: "Implement this settings screen in SwiftUI with NavigationStack."
25
- route_to: "swift-ios"
26
- why: "Native platform implementation; this skill is the web translation of Apple's design talks."
27
- - prompt: "Fix the data race in this actor."
28
- route_to: "write-swift"
29
- why: "Swift language semantics, unrelated to interface feel."
30
-
31
- capability:
32
- - scenario: "User asks for an iOS-style sheet on the web and the project has no motion library installed."
33
- expect: "Names what genuinely needs a spring (the gesture release, the interruption) versus what CSS can do, projects the landing point from release velocity rather than snapping from the release position, hands the velocity to the spring, and ships prefers-reduced-motion plus prefers-reduced-transparency variants with it."
@@ -1,104 +0,0 @@
1
- ---
2
- name: ask-sonner
3
- description: "Sonner, the React toast library: mounting Toaster once, toast() calls, promise and loading toasts, updating, dismissing, styling, theming, dark mode, positioning. Use when toasts do not appear, appear twice, sit behind a modal, ignore Tailwind classes, or do nothing from a server action."
4
- tags: [sonner, toasts, notifications, react, library]
5
- recommends: [react, nextjs, pick-ui-library]
6
- profiles: [full]
7
- origin: emilkowalski (MIT, adapted)
8
- ---
9
-
10
- # Working With Sonner
11
-
12
- > **SDD gate.** If this fired on a **new, non-trivial feature or behaviour change** and there is
13
- > **no approved spec + plan** under `02-DOCS/wiki/sdd/`, hand off to `../specify/SKILL.md` first —
14
- > it runs brainstorm → spec → plan → tasks before any code, then routes back here once the plan is
15
- > approved. Build straight from here only for a genuinely one-line / low-risk change, or a bug fix
16
- > restoring intended behaviour. Method: `../sdd/SKILL.md`.
17
-
18
- **Hand-offs.** Choosing between libraries at all is `../pick-ui-library/SKILL.md`'s (its answer for
19
- toasts is this one). Customising the toast's motion beyond the props here is `../animate/SKILL.md`'s.
20
- App wiring is `../react/SKILL.md`'s and `../nextjs/SKILL.md`'s.
21
-
22
- A guide skill for [Sonner](https://sonner.emilkowal.ski), the toast library. When a task involves Sonner — wiring it up, rendering toasts, styling them, or fixing them — answer from this file first. Full prop tables for `<Toaster />` and `toast()` live in [references/api.md](references/api.md); read it when you need an exact prop name, type, or default.
23
-
24
- ## Setup
25
-
26
- Two pieces, and only two:
27
-
28
- 1. **One `<Toaster />`, mounted once**, as close to the root as possible (in Next.js: `layout.tsx` — it works inside server components). Never render it per-page or conditionally; a second mounted Toaster duplicates every toast.
29
- 2. **`toast()` called from client code** — event handlers, effects, callbacks. It's a plain function, no hook or provider needed, but it does nothing on the server: in a server action, return the result and call `toast()` in the client code that receives it.
30
-
31
- ```jsx
32
- import { Toaster } from 'sonner'; // once, in layout
33
- import { toast } from 'sonner'; // anywhere client-side
34
- ```
35
-
36
- ## Picking the right call
37
-
38
- | You want | Call |
39
- | --- | --- |
40
- | Plain message | `toast('Title')` — add `{ description }` for a second line |
41
- | Success / error / info / warning icon | `toast.success('…')`, `toast.error('…')`, etc. |
42
- | Spinner while you manage state yourself | `toast.loading('…')`, then update it by id |
43
- | Loading → success/error tied to a promise | `toast.promise(promise, { loading, success, error })` — success/error accept functions receiving the resolved value/error |
44
- | Button that does something | `{ action: { label, onClick } }` — closes the toast unless `onClick` calls `event.preventDefault()`; `cancel` is the secondary variant |
45
- | Custom JSX, default toast shell | `toast(<jsx />)` |
46
- | Custom JSX, no styles at all | `toast.custom((t) => <jsx />)` — headless, `t` gives you the id to dismiss |
47
-
48
- ## Recipes
49
-
50
- **Update a toast** — call `toast()` again with the same `id`; only the props you pass change. Switching to `toast.success(…, { id })` changes the type. This is how loading → success flows work without `toast.promise`:
51
-
52
- ```jsx
53
- const id = toast.loading('Uploading…');
54
- toast.success('Uploaded', { id });
55
- ```
56
-
57
- **Persist** — `{ duration: Infinity }`. **Dismiss** — `toast.dismiss(id)`, or `toast.dismiss()` for all. **Read active toasts** — `useSonner()` in React, `toast.getActiveToasts()` outside it.
58
-
59
- **Links or components in the text** — pass a function for the title or description: `toast(() => <a href="…">View</a>)`.
60
-
61
- **Multiple toasters** — give each an `id` and target with `toast('…', { toasterId: 'canvas' })`. Without `toasterId`, every toaster renders the toast.
62
-
63
- **Close callbacks** — `onDismiss` fires on close button or swipe; `onAutoClose` fires on timeout. They are separate; there is no single "closed" callback.
64
-
65
- ## Styling — the escalation ladder
66
-
67
- Climb only as far as the change requires; jumping to the top rung too early is fine (it's the recommended end state), lingering in the middle is not.
68
-
69
- 1. **Defaults** — plus `richColors` on the Toaster for colorful success/error, `invert` to flip against the theme.
70
- 2. **Inline tweaks** — `toastOptions={{ style: {…} }}` on the Toaster for all toasts, or `style` per `toast()` call.
71
- 3. **Classes on parts** — `toastOptions={{ classNames: { toast, title, description, actionButton, cancelButton, closeButton } }}`. Sonner's injected styles win the cascade, so every class needs `!important` (Tailwind: `!text-red-900`). If you're marking more than a few things important, stop — go headless.
72
- 4. **Headless** — `toast.custom()` with your own JSX, keeping Sonner's positioning, stacking, and swipe. The recommended approach for a design-system toast: wrap it in your own `toast()` abstraction. (`unstyled: true` exists as a halfway house, but headless gives more control for the same effort.)
73
-
74
- **Icons** — swap defaults per-type with the Toaster's `icons` prop, per-toast with `icon`, remove with `null`.
75
-
76
- **Theme** — `theme` defaults to `'light'` and does not track the OS. Pass `theme="system"`, or wire your theme provider: `<Toaster theme={resolvedTheme} />` from `next-themes`.
77
-
78
- ## Troubleshooting
79
-
80
- | Symptom | Cause → fix |
81
- | --- | --- |
82
- | Toast never appears | No `<Toaster />` mounted, or it unmounted (conditional render, per-page placement). Mount one at the root. If calling from a server action: `toast()` is client-only — call it with the action's result on the client. |
83
- | Same toast appears twice | Two Toasters mounted (layout **and** page) — keep one. Or `toast()` fired in an effect under React StrictMode's dev double-invoke — fire from the event handler instead, or pass a stable `id` so the second call updates rather than duplicates. |
84
- | Tailwind/CSS classes have no effect | Default styles override them. Mark them `!important`, or use `unstyled` / headless (see the ladder above). |
85
- | Toasts render completely unstyled (common in Astro, view transitions) | Sonner's injected stylesheet was lost — import it explicitly in a layout: `import 'sonner/dist/styles.css'`. |
86
- | Unstyled inside Shadow DOM | Styles land in `document.head`, not the shadow root. Copy the style tag whose text includes `[data-sonner-toaster]` into the shadow root. |
87
- | Toast behind a modal/overlay, or clipped | An ancestor creates a stacking context (`transform`, `filter`, `overflow`) or the overlay out-z-indexes the toaster. Move `<Toaster />` to the document root, outside any dialog/portal container. |
88
- | Dark mode ignored | `theme` defaults to `'light'` — set `theme="system"` or pass the resolved theme (see Theme above). |
89
- | Success/error look gray, not green/red | That's the default. Add `richColors` to the Toaster. |
90
- | Toast never closes | `duration: Infinity`, `dismissible: false`, or a `toast.promise` whose promise never settles — the loading toast waits forever. |
91
- | `toast.promise` stuck on loading | It needs a promise (or a function returning one) as its first argument, and the promise must actually resolve/reject. |
92
- | Swipe-to-dismiss goes the wrong way / doesn't work | Directions derive from `position`. Override with `swipeDirections` on the Toaster. |
93
- | Toast shows up in every toaster | Multiple toasters need targeting: give each Toaster an `id` and pass `toasterId` in the `toast()` call. |
94
- | Toasts too close to the screen edge on mobile | `offset` (desktop, default 32px) and `mobileOffset` (<600px, default 16px) — numbers, CSS strings, or per-side objects. |
95
-
96
- ---
97
-
98
- ## Credit
99
-
100
- Adapted for the rsc catalog from **[emilkowalski/skills](https://github.com/emilkowalski/skills)**
101
- (MIT, commit `d23d7f8`) by Emil Kowalski — the design-engineering and animation philosophy behind
102
- [Sonner](https://sonner.emilkowal.ski), [Vaul](https://vaul.emilkowal.ski) and
103
- [animations.dev](https://animations.dev). The craft bar here is his; the routing, gates and evals
104
- that make it an rsc skill are not. Nothing in this body is a substitute for his course.
@@ -1,33 +0,0 @@
1
- skill: ask-sonner
2
-
3
- should_trigger:
4
- - prompt: "Set up Sonner in this Next.js app."
5
- why: "Sonner setup: the skill's core use case, and the Toaster-in-layout rule is the first thing it says."
6
- - prompt: "My toasts appear twice — what did I do wrong?"
7
- why: "A named symptom with a known cause (a second mounted Toaster)."
8
- - prompt: "How do I show a loading toast that resolves when the promise finishes?"
9
- why: "Promise and loading toasts are explicitly in scope."
10
- - prompt: "My toast is rendering behind the modal."
11
- why: "One of the named common failures the skill exists to fix."
12
- - prompt: "The Tailwind classes I pass to my toast are being ignored."
13
- why: "A named styling failure with a specific answer."
14
- - prompt: "Make the toasts follow our dark mode."
15
- why: "Theming is an explicit section."
16
-
17
- should_not_trigger:
18
- - prompt: "What should I use for toasts in this app?"
19
- route_to: "pick-ui-library"
20
- why: "The choice between libraries comes first; its answer for toasts is this skill."
21
- - prompt: "Change the toast's enter animation curve and make the exit faster."
22
- route_to: "animate"
23
- why: "Custom motion beyond the library's props."
24
- - prompt: "Set up the App Router layout and server actions for this page."
25
- route_to: "nextjs"
26
- why: "App wiring, not the toast library."
27
- - prompt: "Our transactional emails land in spam."
28
- route_to: "email-deliverability"
29
- why: "Also 'notifications', but a completely different surface."
30
-
31
- capability:
32
- - scenario: "User calls toast() from inside a Next.js server action and nothing appears."
33
- expect: "Names the actual cause — toast() does nothing on the server — and gives the fix: return the result from the action and call toast() in the client code that receives it. Does not suggest mounting a second Toaster."
@@ -1,64 +0,0 @@
1
- # Sonner API Reference
2
-
3
- Exact props, types, and defaults. Options passed to `toast()` override the same options set via the Toaster's `toastOptions`.
4
-
5
- ## `<Toaster />`
6
-
7
- | Prop | Type | Default | Description |
8
- | --- | --- | --- | --- |
9
- | `theme` | `string` | `'light'` | `'light'`, `'dark'`, or `'system'`. |
10
- | `richColors` | `boolean` | `false` | Makes error and success states more colorful. |
11
- | `expand` | `boolean` | `false` | Toasts expanded by default (otherwise they expand on hover). |
12
- | `visibleToasts` | `number` | `3` | Amount of visible toasts. |
13
- | `id` | `string` | – | Toaster id, targeted by `toast()`'s `toasterId` option. |
14
- | `position` | `string` | `'bottom-right'` | `top-left`, `top-center`, `top-right`, `bottom-left`, `bottom-center`, `bottom-right`. |
15
- | `closeButton` | `boolean` | `false` | Adds a close button to all toasts. |
16
- | `offset` | `string \| number \| object` | `'32px'` | Offset from screen edges. Object form is per-side: `{ bottom: '24px', right: '16px' }`. |
17
- | `mobileOffset` | `string \| number \| object` | `'16px'` | Offset when screen width < 600px. |
18
- | `swipeDirections` | `array` | based on position | Allowed swipe-to-dismiss directions. |
19
- | `dir` | `string` | `'ltr'` | Text directionality. |
20
- | `hotkey` | `string` | `⌥/alt + T` | Keyboard shortcut that focuses the toaster area. |
21
- | `invert` | `boolean` | `false` | Dark toasts in light mode and vice versa. |
22
- | `toastOptions` | `object` | – | Default options applied to every toast (any `toast()` option below). |
23
- | `gap` | `number` | `14` | Gap between toasts when expanded. |
24
- | `icons` | `object` | – | Replace default icons: `{ success, info, warning, error, loading }`; `null` removes one. |
25
-
26
- ## `toast()` options
27
-
28
- `toast(message, options)` — message is a string, JSX, or a function returning JSX. Returns the toast's id.
29
-
30
- | Option | Type | Default | Description |
31
- | --- | --- | --- | --- |
32
- | `description` | `ReactNode` | – | Renders underneath the title; also accepts a function returning JSX. |
33
- | `closeButton` | `boolean` | `false` | Adds a close button. |
34
- | `invert` | `boolean` | `false` | Dark toast in light mode and vice versa. |
35
- | `duration` | `number` | `4000` | Milliseconds before auto-close. `Infinity` persists the toast. |
36
- | `position` | `string` | `'bottom-right'` | Position of this toast. |
37
- | `dismissible` | `boolean` | `true` | If `false`, the user cannot dismiss the toast. |
38
- | `icon` | `ReactNode` | – | Icon in front of the text; `null` removes the default. |
39
- | `action` | `ReactNode \| { label, onClick }` | – | Primary button; clicking closes the toast unless `onClick` calls `event.preventDefault()`. |
40
- | `cancel` | `ReactNode \| { label, onClick }` | – | Secondary button; clicking closes the toast. |
41
- | `actionButtonStyle` | `object` | `{}` | Styles for the action button. |
42
- | `cancelButtonStyle` | `object` | `{}` | Styles for the cancel button. |
43
- | `id` | `string` | – | Custom id; calling `toast()` again with the same id updates the existing toast. |
44
- | `testId` | `string` | – | Rendered as `data-testid` for e2e tests. |
45
- | `toasterId` | `string` | – | Id of the toaster to render this toast in. |
46
- | `style` | `object` | – | Inline styles for the toast. |
47
- | `classNames` | `object` | – | Classes per part: `{ toast, title, description, actionButton, cancelButton, closeButton }`. Needs `!important` unless `unstyled`. |
48
- | `unstyled` | `boolean` | `false` | Removes all default styles. |
49
- | `onDismiss` | `(toast) => void` | – | Fires when the close button is clicked or the toast is swiped away. |
50
- | `onAutoClose` | `(toast) => void` | – | Fires when the toast closes automatically after `duration`. |
51
- | `containerAriaLabel` | `string` | `'Notifications'` | ARIA label for the toast container. |
52
-
53
- ## Functions
54
-
55
- | Function | Purpose |
56
- | --- | --- |
57
- | `toast(message, opts?)` | Render a toast; returns its id. |
58
- | `toast.success / .error / .info / .warning(message, opts?)` | Typed toast with matching icon. |
59
- | `toast.loading(message, opts?)` | Toast with a spinner; update it by id. |
60
- | `toast.promise(promise, { loading, success, error })` | Loading toast that resolves with the promise; `success`/`error` accept strings, JSX, functions of the result, or objects of toast options. |
61
- | `toast.custom((t) => jsx, opts?)` | Headless toast — your JSX, Sonner's behavior. |
62
- | `toast.dismiss(id?)` | Dismiss one toast, or all when called without an id. |
63
- | `toast.getActiveToasts()` | All active toasts, usable outside React. |
64
- | `useSonner()` | React hook returning `{ toasts }`. |
@@ -1,88 +0,0 @@
1
- ---
2
- name: design-eng
3
- description: "Umbrella for frontend craft: routes a UI request to the sibling that owns it — build motion, review it, audit it, find where it is missing, name an effect, gesture physics, prototype variants, pick a library. Use when an app works but feels cheap or unpolished, or when a frontend request could belong to several of them."
4
- tags: [design, frontend, ui, routing, craft]
5
- recommends: [design-loop, animate, review-animations, apple-design, design]
6
- profiles: [core, full]
7
- origin: emilkowalski (MIT, adapted)
8
- ---
9
-
10
- # Design Engineering — the frontend craft umbrella
11
-
12
- *Two jobs: route the request to the sibling that owns it, and carry the philosophy the siblings assume.*
13
-
14
- > **SDD gate.** This skill routes; it does not outrank the chain. If the request is a **new,
15
- > non-trivial feature or behaviour change** and there is no approved spec + plan under
16
- > `02-DOCS/wiki/sdd/`, hand off to `../specify/SKILL.md` first — it runs brainstorm → spec → plan
17
- > → tasks before any code, then routes back here. Route straight through only for a genuinely
18
- > one-line / low-risk change, or a bug fix restoring intended behaviour. Method: `../sdd/SKILL.md`.
19
-
20
- ## Route first
21
-
22
- Installed in every repo with a frontend, so it fires on requests that could belong to several
23
- siblings. Name the sibling, hand over, and stop — do not do their work here.
24
-
25
- | The request is… | Route to | Not to |
26
- | --- | --- | --- |
27
- | "animate this", "make it feel alive", build a transition | `../animate/SKILL.md` | not `design`, which sets intent and budget, not the implementation |
28
- | the same, but React Native / Expo | `../animate-expo/SKILL.md` | not `animate` — no hover, two runtimes, gestures are the baseline |
29
- | "review the motion in this diff / component" | `../review-animations/SKILL.md` | not `code-review`, which does not know the motion bar |
30
- | "audit the animations across the app", "give me a roadmap" | `../improve-animations/SKILL.md` | not `review-animations`, whose object is one diff |
31
- | "what could be animated here?", "make this feel more alive" | `../find-animation-opportunities/SKILL.md` | not `animate` — nothing has been chosen yet |
32
- | "what's it called when…", naming an effect to prompt with | `../animation-vocabulary/SKILL.md` | — |
33
- | gesture physics, springs, momentum, translucent materials, Apple-grade feel | `../apple-design/SKILL.md` | not `animate`, which owns curves and durations, not physics |
34
- | "show me a few versions of this and let me pick" | `../prototype/SKILL.md` | — |
35
- | "what should I use for toasts / drag and drop / a command menu?" | `../pick-ui-library/SKILL.md` | not hand-rolling the component |
36
- | Sonner specifically — setup, styling, toasts misbehaving | `../ask-sonner/SKILL.md` | — |
37
- | "make it as good as <site>", a named reference to beat, "it looks generic" | `../design-loop/SKILL.md` | not `design` — the bar and the critics are the method, not the visual system |
38
- | "keep this style", "make more like this", output drifted from a look that once worked | `../design-dna/SKILL.md` | not `design-loop` — the winner already exists; this keeps it |
39
- | visual system, brand grounding, landing composition, a whole page | `../design/SKILL.md` | not here — this skill owns craft and motion, not the visual system |
40
- | the App Router / React build itself | `../nextjs/SKILL.md` | — |
41
- | Swift language work (concurrency, generics) | `../write-swift/SKILL.md` | not `swift-ios`, which owns the platform |
42
-
43
- Three rules for routing:
44
-
45
- 1. **Ambiguity resolves to the narrower object.** "Improve the animations" on a diff is
46
- `review-animations`; on a whole codebase it is `improve-animations`. Say which you chose and
47
- which you passed over — never run both.
48
- 2. **When nothing fits, say so.** A tangential hand-off is worse than none. Pure backend, data or
49
- infra work with no UI surface: decline, there is nothing to design.
50
- 3. **Route once, then get out of the way.** The sibling owns the work from there; this body is
51
- background, not a second opinion mid-task.
52
-
53
- The rest of this file is the philosophy the siblings assume — read it when the work needs the
54
- *why* behind a rule, not to re-derive a rule the sibling already states.
55
-
56
- ## The craft posture
57
-
58
- You are a design engineer with the craft sensibility. You build interfaces where every detail compounds into something that feels right. You understand that in a world where everyone's software is good enough, taste is the differentiator.
59
-
60
- ## Why craft, at all
61
-
62
- In a world where everyone's software is good enough, taste is the differentiator — and taste is a
63
- trained instinct, not a preference. Most of the details that produce it are ones users never
64
- consciously notice, which is the point: when something works exactly as someone assumed it would,
65
- they never think about it. The full argument, with the Paul Graham line it rests on, moved to
66
- `../animate/SKILL.md` along with the material it justifies.
67
-
68
- ## Where the craft itself lives
69
-
70
- This skill decides **who answers**. It does not carry their material — that would make every install
71
- pay for a manual it may never open, and it would duplicate the sibling that owns it.
72
-
73
- | You need | It lives in |
74
- |---|---|
75
- | Should this animate at all — the frequency gate, purpose, easing, duration | `../animate/SKILL.md` |
76
- | Springs, component principles, transforms, `clip-path`, gestures, performance, accessibility, stagger, debugging | `../animate/SKILL.md` |
77
- | The required Before/After review format and the checklist of 11 defects | `../review-animations/SKILL.md` |
78
-
79
- If the sibling you need is not installed, name it and offer to install it. A route to something
80
- absent is not a route.
81
-
82
- ## Credit
83
-
84
- Adapted for the rsc catalog from **[emilkowalski/skills](https://github.com/emilkowalski/skills)**
85
- (MIT, commit `d23d7f8`) by Emil Kowalski — the design-engineering and animation philosophy behind
86
- [Sonner](https://sonner.emilkowal.ski), [Vaul](https://vaul.emilkowal.ski) and
87
- [animations.dev](https://animations.dev). The craft bar here is his; the routing, gates and evals
88
- that make it an rsc skill are not. Nothing in this body is a substitute for his course.