@ericrisco/rsc 1.0.26 → 1.0.28

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 (53) hide show
  1. package/README.md +22 -4
  2. package/manifest.json +231 -3
  3. package/package.json +2 -1
  4. package/scripts/build-manifest.js +20 -4
  5. package/scripts/detect-repo.js +27 -1
  6. package/scripts/lib/domains.js +3 -2
  7. package/scripts/lib/skill-lint.js +29 -0
  8. package/scripts/lib/spec-gate.js +122 -0
  9. package/scripts/spec-gate.js +58 -0
  10. package/skills/animate/SKILL.md +228 -0
  11. package/skills/animate/evals/cases.yaml +36 -0
  12. package/skills/animate/references/recipes.md +324 -0
  13. package/skills/animate-expo/SKILL.md +281 -0
  14. package/skills/animate-expo/evals/cases.yaml +36 -0
  15. package/skills/animate-expo/references/recipes.md +385 -0
  16. package/skills/animation-vocabulary/SKILL.md +189 -0
  17. package/skills/animation-vocabulary/evals/cases.yaml +31 -0
  18. package/skills/apple-design/SKILL.md +301 -0
  19. package/skills/apple-design/evals/cases.yaml +33 -0
  20. package/skills/ask-sonner/SKILL.md +104 -0
  21. package/skills/ask-sonner/evals/cases.yaml +33 -0
  22. package/skills/ask-sonner/references/api.md +64 -0
  23. package/skills/clarify/SKILL.md +26 -2
  24. package/skills/design/SKILL.md +8 -0
  25. package/skills/design/references/motion-and-interaction.md +14 -3
  26. package/skills/design/references/visual-system.md +4 -3
  27. package/skills/design-eng/SKILL.md +724 -0
  28. package/skills/design-eng/evals/cases.yaml +36 -0
  29. package/skills/expo/SKILL.md +6 -0
  30. package/skills/find-animation-opportunities/SKILL.md +150 -0
  31. package/skills/find-animation-opportunities/evals/cases.yaml +31 -0
  32. package/skills/improve-animations/SKILL.md +126 -0
  33. package/skills/improve-animations/evals/cases.yaml +31 -0
  34. package/skills/improve-animations/references/audit.md +115 -0
  35. package/skills/improve-animations/references/plan-template.md +73 -0
  36. package/skills/pick-ui-library/SKILL.md +100 -0
  37. package/skills/pick-ui-library/evals/cases.yaml +33 -0
  38. package/skills/prototype/SKILL.md +113 -0
  39. package/skills/prototype/evals/cases.yaml +31 -0
  40. package/skills/prototype/references/picker.md +197 -0
  41. package/skills/react/SKILL.md +6 -0
  42. package/skills/react-native/SKILL.md +5 -0
  43. package/skills/review-animations/SKILL.md +137 -0
  44. package/skills/review-animations/evals/cases.yaml +34 -0
  45. package/skills/review-animations/references/standards.md +187 -0
  46. package/skills/review-animations/scripts/verify.sh +164 -0
  47. package/skills/specify/SKILL.md +77 -26
  48. package/skills/specify/evals/cases.yaml +4 -4
  49. package/skills/specify/references/eliciting-requirements.md +50 -10
  50. package/skills/specify/references/spec-template.md +19 -5
  51. package/skills/swift-ios/SKILL.md +7 -0
  52. package/skills/write-swift/SKILL.md +413 -0
  53. package/skills/write-swift/evals/cases.yaml +36 -0
@@ -0,0 +1,100 @@
1
+ ---
2
+ name: pick-ui-library
3
+ description: "Pick the frontend library for a task from a curated list: dialogs and popovers, command palette, toasts, one-time-password input, charts, virtualization, drag and drop, state, conditional classNames, variants, dark mode. Use instead of hand-rolling a component or installing an abandoned package."
4
+ tags: [frontend, libraries, dependencies, react, ui]
5
+ recommends: [react, nextjs, animate]
6
+ profiles: [full]
7
+ origin: emilkowalski (MIT, adapted)
8
+ ---
9
+
10
+ # Picking The Right Library
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.** Animating what you install is `../animate/SKILL.md`'s. Sonner specifically has its own
19
+ guide, `../ask-sonner/SKILL.md`. Framework-level architecture is `../react/SKILL.md`'s and
20
+ `../nextjs/SKILL.md`'s — this skill picks the dependency, it does not design the app.
21
+
22
+ A lookup skill. When invoked with a task ("I need toasts", "what should I use for drag and drop?"), match the task to the curated list below and recommend the library. These are deliberate, taste-driven picks — don't substitute alternatives outside this list unless the user asks for one or the task genuinely isn't covered.
23
+
24
+ ## How to use this
25
+
26
+ 1. **Identify the task**, not the library the user named. "I need to show a dropdown" is a UI-primitives task (base-ui), even if they asked about something else.
27
+ 2. **Check what's already installed.** Look at `package.json` first. If the project already uses a listed library, use it. If it uses a competitor (e.g. react-window instead of Virtuoso), flag the recommendation but don't churn the dependency without being asked.
28
+ 3. **Recommend one library**, state what it's for in one sentence, and install/wire it up if that's part of the request. Don't present a menu of options when the list has a clear answer.
29
+ 4. If the task isn't covered by the list, say so explicitly and recommend from your own knowledge — but be clear you've left the curated list.
30
+
31
+ ## The list
32
+
33
+ ### UI components & primitives
34
+
35
+ | Task | Library |
36
+ | --- | --- |
37
+ | Unstyled, accessible UI components (dialogs, popovers, menus, selects…) | [base-ui](https://base-ui.com) |
38
+ | Command menus (⌘K palettes) | [cmdk](https://cmdk.paco.me) |
39
+ | Toasts / notifications | [Sonner](https://sonner.emilkowal.ski) |
40
+ | One-time password / verification code inputs | [input-otp](https://input-otp.rodz.dev) |
41
+ | Customizable GUIs / control panels | [Leva](https://github.com/pmndrs/leva) — [dialkit](https://joshpuckett.me/dialkit) is an alternative |
42
+
43
+ ### Motion & visuals
44
+
45
+ | Task | Library |
46
+ | --- | --- |
47
+ | General-purpose animation (springs, layout animations, enter/exit) | [motion](https://motion.dev) (Framer Motion) |
48
+ | Animating numbers (counters, prices, stats) | [NumberFlow](https://number-flow.barvian.me) |
49
+ | Animated text components | [torph](https://torph.lochie.me/) |
50
+ | 3D globes | [Cobe](https://cobe.vercel.app) |
51
+ | Dynamic OG images (HTML/CSS → SVG/PNG) | [Satori](https://github.com/vercel/satori) |
52
+ | Syntax highlighting | [shiki](https://shiki.style) |
53
+
54
+ Reach for motion when you need springs, layout animations, exit animations, or gesture-driven values. A simple hover or fade doesn't need it — plain CSS transitions are the right tool there.
55
+
56
+ ### Charts
57
+
58
+ | Task | Library |
59
+ | --- | --- |
60
+ | Real-time / streaming charts | [Liveline](https://github.com/benjitaylor/liveline) |
61
+ | General charts (static or interactive dashboards) | [recharts](https://recharts.org) |
62
+
63
+ The split: if data points arrive live and the chart scrolls with time, use Liveline. Everything else is recharts.
64
+
65
+ ### Interaction & performance
66
+
67
+ | Task | Library |
68
+ | --- | --- |
69
+ | Drag and drop | [dnd kit](https://dndkit.com) |
70
+ | Virtualization (long lists, large tables) | [Virtuoso](https://virtuoso.dev) |
71
+
72
+ ### State & styling
73
+
74
+ | Task | Library |
75
+ | --- | --- |
76
+ | State management | [zustand](https://zustand.docs.pmnd.rs) |
77
+ | Constructing `className` strings conditionally | [clsx](https://github.com/lukeed/clsx) |
78
+ | Type-safe, variant-driven styling for Tailwind | [cva](https://cva.style) |
79
+ | Theme switching / dark mode (no flash on load) | [next-themes](https://github.com/pacocoursey/next-themes) |
80
+
81
+ The styling split: clsx for ad-hoc conditional classes; cva when a component has real variants (size, intent, state) that deserve a typed API. They compose — cva uses clsx-style inputs internally.
82
+
83
+ ## Common mismatches to catch
84
+
85
+ - **Toasts built by hand or with a modal library** → Sonner exists for exactly this.
86
+ - **A `<div>`-based dropdown/dialog with manual focus handling** → base-ui, which handles accessibility, focus trapping, and dismissal.
87
+ - **Animating a number by re-rendering text** → NumberFlow handles digit transitions properly.
88
+ - **Rendering a 1,000+ row list directly** → Virtuoso before reaching for pagination hacks.
89
+ - **A `useState`-per-component web of props for shared state** → zustand.
90
+ - **Template-literal className ternaries three conditions deep** → clsx (or cva if it's variant-shaped).
91
+
92
+ ---
93
+
94
+ ## Credit
95
+
96
+ Adapted for the rsc catalog from **[emilkowalski/skills](https://github.com/emilkowalski/skills)**
97
+ (MIT, commit `d23d7f8`) by Emil Kowalski — the design-engineering and animation philosophy behind
98
+ [Sonner](https://sonner.emilkowal.ski), [Vaul](https://vaul.emilkowal.ski) and
99
+ [animations.dev](https://animations.dev). The craft bar here is his; the routing, gates and evals
100
+ that make it an rsc skill are not. Nothing in this body is a substitute for his course.
@@ -0,0 +1,33 @@
1
+ skill: pick-ui-library
2
+
3
+ should_trigger:
4
+ - prompt: "What should I use for toasts in this React app?"
5
+ why: "A task-to-library lookup, the skill's core use case."
6
+ - prompt: "Which library for drag and drop? I don't want to hand-roll it."
7
+ why: "Named task with a curated answer; hand-rolling is the mismatch the skill catches."
8
+ - prompt: "I need a command palette like Raycast's — what do I install?"
9
+ why: "Command menus are an explicit row in the list."
10
+ - prompt: "This list has 5000 rows and it's crawling. What do people use for virtualization?"
11
+ why: "Virtualization is an explicit row, and the 1000+ row case is a named mismatch."
12
+ - prompt: "We need a one-time-password input for the verification screen."
13
+ why: "OTP inputs are an explicit row."
14
+ - prompt: "What's the right pick for charts in a dashboard, and does live data change the answer?"
15
+ why: "Charts, with the streaming-versus-static split the list makes explicit."
16
+
17
+ should_not_trigger:
18
+ - prompt: "Animate the toast so it slides in and can be swiped away."
19
+ route_to: "animate"
20
+ why: "Motion on an already-chosen component."
21
+ - prompt: "Fix the toasts — they appear twice and lose their Tailwind classes."
22
+ route_to: "ask-sonner"
23
+ why: "Sonner-specific troubleshooting has its own guide."
24
+ - prompt: "Restructure where state lives across these React components and fix the re-render storm."
25
+ route_to: "react"
26
+ why: "App architecture, not a dependency choice."
27
+ - prompt: "Which Postgres extension should we use for vector search?"
28
+ route_to: "vector-db"
29
+ why: "Also a 'which library' question, but a data-layer one outside this list."
30
+
31
+ capability:
32
+ - scenario: "The project's package.json already uses react-window, and the user asks what to use for virtualizing a large table."
33
+ expect: "Reads package.json first, names the curated pick, and flags that a competitor is already installed without churning the dependency unasked. If the task were genuinely outside the curated list, it says so explicitly instead of pretending the list covers it."
@@ -0,0 +1,113 @@
1
+ ---
2
+ name: prototype
3
+ description: "Build several genuinely different versions — variants, takes, options, directions — of one UI piece behind a visual picker, so you flip through them live and promote the winner. Use to explore a look before committing, or when you do not yet know what you want something to look like."
4
+ tags: [design, variants, exploration, picker, ui]
5
+ recommends: [design, animate, nextjs]
6
+ profiles: [full]
7
+ origin: emilkowalski (MIT, adapted)
8
+ ---
9
+
10
+ # Prototyping Variants
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.** Once a variant wins, its motion should survive `../review-animations/SKILL.md`. The
19
+ visual system the variants inherit is `../design/SKILL.md`'s. Dependencies belong to
20
+ `../pick-ui-library/SKILL.md` — do not install a library to explore a layout.
21
+
22
+ A divergence skill. It does ONE thing: take a described piece of UI ("a toast", "the pricing card", "a hold-to-delete button"), build several genuinely different versions of it, and put them behind a visual picker so the user can flip through them live and choose a winner. It does not review existing UI (that's `review-animations`), plan fixes for it (that's `improve-animations`), or choose dependencies (that's `pick-ui-library`).
23
+
24
+ ## Operating Posture
25
+
26
+ You are a senior design engineer running a design exploration. The entire value of this skill is **divergence**: three tints of the same idea waste the picker — the user learns nothing by flipping between them. Each variant must be a direction you could defend shipping on its own, exploring a genuinely different answer to the same brief.
27
+
28
+ Divergence is not an excuse to drop the craft bar. Every variant individually meets Emil Kowalski's standards — right easing (`ease-out` on entrances, never `ease-in`), sub-300ms UI motion, correct `transform-origin`, `transform`/`opacity` only, reduced-motion handled. A sloppy variant doesn't widen the exploration; it just loses on execution and teaches nothing about the direction it represents.
29
+
30
+ ## Hard Rules
31
+
32
+ 1. **Never touch production code during exploration.** Everything lives in an isolated prototype surface (see Phase 4). Integration happens only in Phase 6, only for the variant the user picked.
33
+ 2. **Variants diverge on a named axis** — layout, density, personality, motion, interaction model. Before building, you must be able to state each variant's axis in a phrase. Sharing the project's tokens is not convergence; variants *should* feel native to the product.
34
+ 3. **Every variant fully works.** Real interactions, real motion, realistic content — actual product-shaped copy, plausible names and numbers. No lorem ipsum, no dead buttons, no "imagine this part".
35
+ 4. **The picker is chrome, not a contestant.** Its exact markup, styles, and behavior are specified in [references/picker.md](references/picker.md) — copy them verbatim. Its look is not a design decision and never adapts to the project.
36
+ 5. **Clean up after the choice.** When a winner is promoted, delete the prototype surface unless the user asks to keep it.
37
+
38
+ ## Workflow
39
+
40
+ ### Phase 1 — Scope
41
+
42
+ One thing per run. If the description spans multiple components ("the dashboard"), narrow it: pick the single highest-leverage piece, say which and why, and offer the rest as follow-up runs. Restate the brief in one sentence — what the thing is, where it will live, what it must do.
43
+
44
+ ### Phase 2 — Recon
45
+
46
+ Before designing anything, map the ground the variants must stand on:
47
+
48
+ - **Stack**: framework, styling system (Tailwind, CSS modules, vanilla), motion library if any.
49
+ - **Tokens**: colors, radii, spacing, fonts, easing/duration variables. Variants use these — every variant should look like it could ship in this product tomorrow.
50
+ - **Personality**: playful consumer app or crisp dashboard? This bounds how far the boldest variant may go.
51
+ - **Context**: where the piece renders — against what background, beside what neighbors, at what sizes.
52
+
53
+ If there is no project (empty directory, or the user is just exploring), skip to the standalone branch in Phase 4 and choose a restrained default look: neutral grays, one accent, system font stack.
54
+
55
+ ### Phase 3 — Choose directions
56
+
57
+ Default **3 variants**; up to 5 when the user asks or the design space is genuinely wide. More than 5 dilutes the comparison.
58
+
59
+ Before writing any code, list the set: a name and an axis for each. Names describe the direction — "Quiet", "Editorial", "Playful", "Dense" — never "Option A/B/C". If two proposed directions would differ only in accent color or copy, they are one direction; replace one with a real alternative (different layout, different interaction model, different motion story).
60
+
61
+ **Completion criterion:** every variant has a name and a stated axis, and no two variants share an axis position.
62
+
63
+ ### Phase 4 — Build the picker harness
64
+
65
+ Two branches, by what exists:
66
+
67
+ - **In a project with a dev server** — an isolated route or page (`/prototypes/<slug>`, or the framework's equivalent), one file per variant plus a small harness file. Nothing imports from the prototype surface into production code.
68
+ - **No project / static context** — a single self-contained HTML file (inline CSS/JS) the user can open directly in a browser.
69
+
70
+ The picker's markup, styles, keyboard wiring, and placement come from [references/picker.md](references/picker.md), verbatim — load it now and build exactly that. Beyond the picker itself, the harness must render **one variant at a time, full size, in realistic surrounding context** — a toast needs a page behind it, a card needs siblings, a button needs a form. Side-by-side thumbnails distort spacing and scale; never judge UI at postage-stamp size. Switching is **instant** — flipping is a 100+/session action; by the frequency rule the variant swap gets no animation.
71
+
72
+ ### Phase 5 — Verify and hand off
73
+
74
+ Run the harness. Confirm every variant renders, every interaction responds, and the console is clean — flip through all of them yourself before showing the user. If browser tooling is available, screenshot each variant.
75
+
76
+ Then present the set and **stop — the choice belongs to the user**:
77
+
78
+ | # | Variant | Axis | When it's the right choice | Its cost |
79
+ | --- | --- | --- | --- | --- |
80
+ | 1 | Quiet | Minimal motion, borders over shadows | The product is a daily-use tool | Least memorable |
81
+ | 2 | Editorial | Large type, generous whitespace | The moment deserves weight | Eats vertical space |
82
+
83
+ Close with where the picker is running (URL or file path) and the keys to flip.
84
+
85
+ **Completion criterion:** every variant is reachable from the picker and behaves correctly; no console errors; the table names each variant's tradeoff honestly.
86
+
87
+ ### Phase 6 — Promote on selection
88
+
89
+ When the user picks: integrate that variant where it belongs, following the project's existing conventions (file layout, naming, token usage), then delete the prototype surface per Hard Rule 5. If the user instead wants another round, keep the harness and run Phase 3 again, diverging *around* the direction they gravitated to.
90
+
91
+ ## Invocation Variants
92
+
93
+ | Invocation | Behavior |
94
+ | --- | --- |
95
+ | `<description>` | Full workflow: scope → recon → 3 variants → picker → wait for choice |
96
+ | `<description> x5` | Same, with that many variants (capped at 5) |
97
+ | `riff <variant>` | New round: keep the harness, generate a fresh set diverging around the named variant's direction |
98
+ | `keep <variant>` | Promote that variant into the codebase and delete the prototype surface |
99
+ | `keep <variant>, leave the picker` | Promote, but keep the prototype surface around |
100
+
101
+ ## Tone
102
+
103
+ Sell each variant honestly — one line on when it wins, one on what it costs. Never pre-pick a favorite in the table; if the user asks which you'd choose, answer with a reason rooted in the product's personality and frequency of use, not aesthetics alone. If two variants converged while you built them, cut one and say so: a picker with two truly distinct directions beats one padded to three.
104
+
105
+ ---
106
+
107
+ ## Credit
108
+
109
+ Adapted for the rsc catalog from **[emilkowalski/skills](https://github.com/emilkowalski/skills)**
110
+ (MIT, commit `d23d7f8`) by Emil Kowalski — the design-engineering and animation philosophy behind
111
+ [Sonner](https://sonner.emilkowal.ski), [Vaul](https://vaul.emilkowal.ski) and
112
+ [animations.dev](https://animations.dev). The craft bar here is his; the routing, gates and evals
113
+ that make it an rsc skill are not. Nothing in this body is a substitute for his course.
@@ -0,0 +1,31 @@
1
+ skill: prototype
2
+
3
+ should_trigger:
4
+ - prompt: "Build me three different versions of this pricing card so I can pick one."
5
+ why: "Several genuinely different versions behind a picker: the skill's core use case."
6
+ - prompt: "I don't know what I want this empty state to look like — show me some options I can click through."
7
+ why: "Divergent exploration with a live picker, which is what this skill builds."
8
+ - prompt: "Give me a few takes on the toast component and let me flip between them."
9
+ why: "'Flip between them' is the picker; a toast is a single, well-scoped piece."
10
+ - prompt: "Prototype four directions for the dashboard header, from quiet to bold."
11
+ why: "Named axis (restraint to boldness) with a variant count inside the cap."
12
+ - prompt: "Explore some designs for the hold-to-delete button before we commit to one."
13
+ why: "Exploration before commitment; production code stays untouched until a winner is picked."
14
+
15
+ should_not_trigger:
16
+ - prompt: "Animate the toast we already chose, with the right curve and duration."
17
+ route_to: "animate"
18
+ why: "A single chosen target; there is nothing to diverge on."
19
+ - prompt: "Review the motion in the variant I picked."
20
+ route_to: "review-animations"
21
+ why: "Judging one implementation, not generating alternatives."
22
+ - prompt: "Design the whole marketing site — hero, pricing, footer, the works."
23
+ route_to: "design"
24
+ why: "A whole-page visual system, far past the one-piece-per-run scope."
25
+ - prompt: "Which drag-and-drop library should we install for this?"
26
+ route_to: "pick-ui-library"
27
+ why: "A dependency decision; this skill does not install libraries to explore a layout."
28
+
29
+ capability:
30
+ - scenario: "User asks for three versions of a card in a project with existing Tailwind tokens and a crisp, dense dashboard personality."
31
+ expect: "States each variant's axis before building, and no two variants share an axis position — three tints of the same idea is the failure mode. Every variant uses the project's tokens, works for real with plausible content, and meets the motion bar individually. Presents the tradeoffs honestly and stops: the choice is the user's."
@@ -0,0 +1,197 @@
1
+ # The Picker
2
+
3
+ The picker's appearance is **not a design decision** — it is this spec. Copy the markup, CSS, and wiring below verbatim; the only values that change per run are the variant names and count. It stays identical across every project so it always reads as harness chrome, never as part of the design being judged. Do not restyle it with the project's tokens, fonts, or colors.
4
+
5
+ It is a floating dark pill, bottom-center. Dark glass works on top of any page — light or dark — which is why it is not theme-aware.
6
+
7
+ ## Markup
8
+
9
+ The sliding highlight span first, one button per variant, a hairline divider, then the replay button (only when at least one variant has motion to re-trigger):
10
+
11
+ ```html
12
+ <nav class="proto-picker" aria-label="Prototype variants">
13
+ <span class="proto-picker-highlight" aria-hidden="true"></span>
14
+ <button class="proto-picker-item" data-active aria-current="true">Quiet</button>
15
+ <button class="proto-picker-item">Editorial</button>
16
+ <button class="proto-picker-item">Playful</button>
17
+ <span class="proto-picker-divider" aria-hidden="true"></span>
18
+ <button class="proto-picker-item proto-picker-replay" aria-label="Replay animation (R)">↻</button>
19
+ </nav>
20
+ ```
21
+
22
+ In a framework, keep the class names and structure; only the rendering syntax changes.
23
+
24
+ ## Styles
25
+
26
+ ```css
27
+ .proto-picker {
28
+ position: fixed;
29
+ bottom: 24px;
30
+ left: 50%;
31
+ transform: translateX(-50%);
32
+ z-index: 2147483647;
33
+ display: flex;
34
+ align-items: center;
35
+ gap: 2px;
36
+ padding: 4px;
37
+ border-radius: 999px;
38
+ background: rgba(10, 10, 10, 0.82);
39
+ -webkit-backdrop-filter: blur(12px) saturate(1.4);
40
+ backdrop-filter: blur(12px) saturate(1.4);
41
+ box-shadow:
42
+ 0 0 0 1px rgba(255, 255, 255, 0.08) inset,
43
+ 0 8px 24px rgba(0, 0, 0, 0.24),
44
+ 0 2px 6px rgba(0, 0, 0, 0.12);
45
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
46
+ font-size: 13px;
47
+ line-height: 1;
48
+ -webkit-font-smoothing: antialiased;
49
+ user-select: none;
50
+ -webkit-user-select: none;
51
+ }
52
+
53
+ .proto-picker-highlight {
54
+ position: absolute;
55
+ top: 4px;
56
+ left: 0;
57
+ height: 28px;
58
+ border-radius: 999px;
59
+ background: rgba(255, 255, 255, 0.12);
60
+ will-change: transform;
61
+ }
62
+
63
+ /* The slide is enabled only after first paint (data-ready), so load doesn't animate. */
64
+ .proto-picker[data-ready] .proto-picker-highlight {
65
+ transition:
66
+ transform 250ms cubic-bezier(0.23, 1, 0.32, 1),
67
+ width 250ms cubic-bezier(0.23, 1, 0.32, 1);
68
+ }
69
+
70
+ @media (prefers-reduced-motion: reduce) {
71
+ .proto-picker[data-ready] .proto-picker-highlight { transition: none; }
72
+ }
73
+
74
+ .proto-picker-item {
75
+ position: relative; /* sits above the highlight */
76
+ display: flex;
77
+ align-items: center;
78
+ height: 28px;
79
+ padding: 0 12px;
80
+ border: 0;
81
+ border-radius: 999px;
82
+ background: transparent;
83
+ color: rgba(255, 255, 255, 0.55);
84
+ font: inherit;
85
+ cursor: pointer;
86
+ transition: color 150ms ease-out;
87
+ }
88
+
89
+ .proto-picker-item:hover {
90
+ color: rgba(255, 255, 255, 0.85);
91
+ }
92
+
93
+ .proto-picker-item:active {
94
+ transform: scale(0.97);
95
+ }
96
+
97
+ .proto-picker-item:focus-visible {
98
+ outline: 2px solid rgba(255, 255, 255, 0.4);
99
+ outline-offset: 2px;
100
+ }
101
+
102
+ .proto-picker-item[data-active] {
103
+ color: #fff;
104
+ }
105
+
106
+ .proto-picker-divider {
107
+ width: 1px;
108
+ height: 16px;
109
+ margin: 0 4px;
110
+ background: rgba(255, 255, 255, 0.12);
111
+ }
112
+
113
+ .proto-picker-replay {
114
+ padding: 0 10px;
115
+ font-size: 14px;
116
+ }
117
+
118
+ .proto-picker[data-position="top"] {
119
+ bottom: auto;
120
+ top: 24px;
121
+ }
122
+ ```
123
+
124
+ ## Rules
125
+
126
+ - **Verbatim.** These values are the spec. No project fonts, no brand colors, no theme switching, no extra shadows or borders.
127
+ - **The highlight slides; the variant swap stays instant.** The active pill animates between buttons (250ms, strong ease-out) as spatial feedback on the picker itself — but the variant being previewed still switches with no transition. The `width` transition is a deliberate exception to the transform/opacity rule: the element is 28px tall, absolutely positioned, and has no layout dependents, so the paint cost is negligible.
128
+ - **One allowed modification:** if a variant occupies the bottom-center of the screen (a toast stack, a bottom sheet, a dock), set `data-position="top"` so the picker never covers the work. Nothing else about it may move or change.
129
+ - **Replay is conditional.** Render the replay button and its divider only when at least one variant has an entrance or state animation worth re-triggering; a static comparison gets a shorter pill.
130
+
131
+ ## Behavior contract
132
+
133
+ The contract is fixed regardless of how the harness renders:
134
+
135
+ - Number keys `1–N` and `←`/`→` switch variants; `R` replays. Ignore key events when focus is in an input, textarea, select, or contenteditable, or when a modifier is held.
136
+ - Clicking an item switches to it; exactly one item carries `data-active` and `aria-current="true"` at all times, and the highlight slides to it.
137
+ - Selection persists across reload via a URL param (`?v=2`), falling back to variant 1. The highlight takes its initial position without animating (`data-ready` is added after first paint).
138
+ - Switching re-mounts the variant (so entrance animations re-run); the replay key re-mounts without switching.
139
+
140
+ ## Reference wiring
141
+
142
+ Verbatim for the standalone-HTML branch; in a framework, keep the same behavior but express it idiomatically (state instead of `innerHTML`, a keyed re-mount instead of `requestAnimationFrame`, refs + a layout effect for the highlight measurement).
143
+
144
+ ```js
145
+ // `variants` is an array of render functions, one per variant, in picker order.
146
+ const stage = document.getElementById('stage');
147
+ const picker = document.querySelector('.proto-picker');
148
+ const highlight = picker.querySelector('.proto-picker-highlight');
149
+ const items = [...picker.querySelectorAll('.proto-picker-item:not(.proto-picker-replay)')];
150
+ const replay = picker.querySelector('.proto-picker-replay');
151
+ let current = 0;
152
+
153
+ function moveHighlight() {
154
+ const el = items[current];
155
+ highlight.style.width = el.offsetWidth + 'px';
156
+ highlight.style.transform = `translateX(${el.offsetLeft}px)`;
157
+ }
158
+
159
+ function mount(i) {
160
+ stage.innerHTML = '';
161
+ // Clear first, render next frame, so entrance animations re-run.
162
+ requestAnimationFrame(() => { stage.innerHTML = variants[i](); });
163
+ }
164
+
165
+ function setActive(i) {
166
+ if (i < 0 || i >= variants.length) return;
167
+ current = i;
168
+ items.forEach((el, j) => {
169
+ el.toggleAttribute('data-active', j === i);
170
+ if (j === i) el.setAttribute('aria-current', 'true');
171
+ else el.removeAttribute('aria-current');
172
+ });
173
+ moveHighlight();
174
+ const url = new URL(location);
175
+ url.searchParams.set('v', i + 1);
176
+ history.replaceState(null, '', url);
177
+ mount(i);
178
+ }
179
+
180
+ items.forEach((el, i) => el.addEventListener('click', () => setActive(i)));
181
+ replay?.addEventListener('click', () => mount(current));
182
+ window.addEventListener('resize', moveHighlight);
183
+
184
+ document.addEventListener('keydown', (e) => {
185
+ if (/^(INPUT|TEXTAREA|SELECT)$/.test(e.target.tagName) || e.target.isContentEditable) return;
186
+ if (e.metaKey || e.ctrlKey || e.altKey) return;
187
+ const num = parseInt(e.key, 10);
188
+ if (num >= 1 && num <= variants.length) setActive(num - 1);
189
+ else if (e.key === 'ArrowRight') setActive((current + 1) % variants.length);
190
+ else if (e.key === 'ArrowLeft') setActive((current - 1 + variants.length) % variants.length);
191
+ else if (e.key === 'r' || e.key === 'R') mount(current);
192
+ });
193
+
194
+ setActive((parseInt(new URLSearchParams(location.search).get('v'), 10) || 1) - 1);
195
+ // Enable the slide only after first paint, so load doesn't animate.
196
+ requestAnimationFrame(() => requestAnimationFrame(() => picker.setAttribute('data-ready', '')));
197
+ ```
@@ -8,6 +8,12 @@ origin: risco
8
8
 
9
9
  # React + Vite SPA — Components, State, Data Fetching (React 19.2, 2026)
10
10
 
11
+ **Hand-off — picking dependencies.** Which library to reach for (UI primitives, command menus,
12
+ toasts, OTP inputs, charts, virtualization, drag and drop, state, styling, theming) is
13
+ `../pick-ui-library/SKILL.md`'s — reach for it before hand-rolling a component. Animating what you
14
+ install is `../animate/SKILL.md`'s.
15
+
16
+
11
17
  > Build or review a fast, typed, correctly-architected **client-side** React single-page app bundled by Vite. No server runtime, no RSC tree, no framework router. Server-rendered React (App Router, server actions, SSR/SSG) is not this skill — that is `../nextjs/SKILL.md`.
12
18
 
13
19
  **SDD gate — before writing feature code.** If this skill fired on a **new, non-trivial feature or behaviour change** and there is **no approved spec + plan** under `02-DOCS/wiki/sdd/`, stop and hand off to `../specify/SKILL.md`: it runs brainstorm → spec → plan → tasks, then routes back here once the plan is approved. Build directly only for a genuinely one-line / low-risk change. Method: `../sdd/SKILL.md`.
@@ -8,6 +8,11 @@ origin: risco
8
8
 
9
9
  # React Native (app code)
10
10
 
11
+ **Hand-off — the app vs its motion.** Animation and gestures in a React Native app — Reanimated
12
+ worklets on the UI runtime, gesture handoff, sheets, press feedback, haptics — are
13
+ `../animate-expo/SKILL.md`'s, whose guidance applies to bare React Native as well as Expo.
14
+
15
+
11
16
  You write the code that runs *inside* the app: render, navigate, animate, list, persist, and author native modules. Mobile is split across two skills by **verb** — run the verb test before doing anything:
12
17
 
13
18
  | Verb in the request | Skill |
@@ -0,0 +1,137 @@
1
+ ---
2
+ name: review-animations
3
+ description: "Review, critique or grade existing animation and motion code against a strict craft bar — easing, duration, transform-origin, interruptibility, GPU-only properties, reduced motion, enter/exit asymmetry, stagger. Default is to flag; approval is earned. Use on the transitions in a diff or a component."
4
+ tags: [motion, animation, review, craft, ui]
5
+ recommends: [animate, improve-animations, code-review]
6
+ profiles: [full]
7
+ origin: emilkowalski (MIT, adapted)
8
+ ---
9
+
10
+ # Reviewing Animations
11
+
12
+ **Hand-offs.** Reviewing a whole codebase's motion, and writing plans another agent can execute, is
13
+ `../improve-animations/SKILL.md`'s — this skill's object is **one diff or one component**. Finding
14
+ motion that is *missing* is `../find-animation-opportunities/SKILL.md`'s. General code review is
15
+ `../code-review/SKILL.md`'s; decline non-motion code and point there.
16
+
17
+ **The countable half of this bar is executable.** `scripts/verify.sh` in this skill carries the tell
18
+ registry — `transition: all`, a `scale(0)` entrance, `ease-in` on UI, a centered popover origin, a
19
+ layout-property transition, the Motion `x`/`y`/`scale` shorthands, and motion with no reduced-motion
20
+ guard anywhere. Run it first (`bash scripts/verify.sh`, add `--strict` to fail on warnings), then
21
+ spend your judgement on what it cannot see: frequency tier, purpose, cohesion, and whether the thing
22
+ feels right. A finding the script already made is not a finding you discovered.
23
+
24
+ A specialized review skill. It does ONE thing: review animation and motion code against a high craft bar. It does not write features, fix unrelated bugs, or review non-motion code. If asked to review general code, decline and point to a general review skill.
25
+
26
+ ## Operating Posture
27
+
28
+ You are a senior design engineer with a brutal eye for craft. Your bias is toward **motion that feels right**, not motion that merely runs. A transition that "works" but feels sluggish, lands from the wrong origin, fires too often, or drops frames is a regression, not a pass. Default to flagging. Approval is earned, not assumed.
29
+
30
+ The substantive bar comes from Emil Kowalski's animation philosophy (animations.dev). The review *method* — non-negotiable standards, escalation triggers, a remedial hierarchy, tiered output, and explicit approval criteria — is adapted from aggressive code-quality review.
31
+
32
+ For the full rule catalog (easing curves, duration tables, spring config, gestures, clip-path, performance, a11y), see [references/standards.md](references/standards.md). Load it whenever a finding needs a precise value or citation.
33
+
34
+ ## The Ten Non-Negotiable Standards
35
+
36
+ Every animation in the diff is measured against these. A violation is a finding.
37
+
38
+ 1. **Justified motion.** Every animation must answer "why does this animate?" — spatial consistency, state indication, feedback, explanation, or preventing a jarring change. "It looks cool" on a frequently-seen element is a block.
39
+
40
+ 2. **Frequency-appropriate.** Match motion to how often it's seen. Keyboard-initiated and 100+/day actions get **no** animation. Tens/day gets reduced motion. Occasional gets standard. Rare/first-time can have delight.
41
+
42
+ 3. **Responsive easing.** Entering/exiting elements use `ease-out` or a strong custom curve. `ease-in` on UI is a block — it delays the moment the user watches most. Built-in CSS easings are too weak; expect custom cubic-beziers.
43
+
44
+ 4. **Sub-300ms UI.** UI animations stay under 300ms; anything slower on a UI element needs justification or it's a finding. Per-element budgets live in [references/standards.md](references/standards.md).
45
+
46
+ 5. **Origin & physical correctness.** Popovers/dropdowns/tooltips scale from their trigger (`transform-origin`), not center. Never animate from `scale(0)` — start from `scale(0.9–0.97)` + opacity (Modals are exempt — they stay centered.)
47
+
48
+ 6. **Interruptibility.** Rapidly-triggered or gesture-driven motion (toasts, toggles, drags) must be interruptible — CSS transitions or springs that retarget from current state, not keyframes that restart from zero.
49
+
50
+ 7. **GPU-only properties.** Animate `transform` and `opacity` only. Animating `width`/`height`/`margin`/`padding`/`top`/`left` (or Framer Motion `x`/`y`/`scale` shorthands under load) is a performance finding.
51
+
52
+ 8. **Accessibility.** `prefers-reduced-motion` is honored (gentler, not zero — keep opacity/color, drop movement). Hover animations are gated behind `@media (hover: hover) and (pointer: fine)`.
53
+
54
+ 9. **Asymmetric enter/exit.** Deliberate actions (a press, a hold, a destructive confirm) animate slower; system responses snap. Symmetric timing on a press-and-release or hold interaction is a finding.
55
+
56
+ 10. **Cohesion.** Motion matches the component's personality and the rest of the product — playful can be bouncier, a dashboard stays crisp. Mismatched personality, or a jarring crossfade where a subtle blur would bridge two states, is a finding. When unsure whether motion feels right, the strongest move is often to delete it.
57
+
58
+ ## Aggressive Escalation Triggers
59
+
60
+ Flag these on sight, hard:
61
+
62
+ - `transition: all` (unbounded property animation)
63
+ - `scale(0)` or pure-fade entrances with no initial transform
64
+ - `ease-in` on any UI interaction; weak built-in easing on a deliberate animation
65
+ - Animation on a keyboard shortcut, command-palette toggle, or 100+/day action
66
+ - UI duration > 300ms with no stated reason
67
+ - `transform-origin: center` on a trigger-anchored popover/dropdown/tooltip
68
+ - Keyframes on toasts, toggles, or anything added/triggered rapidly
69
+ - Animating layout properties (`width`/`height`/`margin`/`padding`/`top`/`left`)
70
+ - Framer Motion `x`/`y`/`scale` props on motion that runs while the page is busy
71
+ - Updating a CSS variable on a parent to drive a child transform (style recalc storm)
72
+ - Missing `prefers-reduced-motion` handling on movement
73
+ - Ungated `:hover` motion
74
+ - Symmetric enter/exit timing on a press-and-release or hold interaction
75
+ - Everything-at-once entrance where a 30–80ms stagger belongs
76
+
77
+ ## Remedial Preference Hierarchy
78
+
79
+ When proposing fixes, prefer earlier moves over later ones:
80
+
81
+ 1. **Delete the animation** (high-frequency / no purpose / keyboard-triggered).
82
+ 2. **Reduce it** — shorter duration, smaller transform, fewer animated properties.
83
+ 3. **Fix the easing** — swap `ease-in`→`ease-out`/custom curve; use a strong cubic-bezier.
84
+ 4. **Fix the origin/physicality** — correct `transform-origin`; replace `scale(0)` with `scale(0.95)`+opacity.
85
+ 5. **Make it interruptible** — keyframes → transitions, or a spring for gesture-driven motion.
86
+ 6. **Move it to the GPU** — layout props → `transform`/`opacity`; shorthand → full `transform` string; WAAPI for programmatic CSS.
87
+ 7. **Asymmetric timing** — slow the deliberate phase, snap the response.
88
+ 8. **Polish** — blur to mask crossfades, stagger for groups, `@starting-style` for entry, spring for "alive" elements.
89
+ 9. **Accessibility & cohesion** — add reduced-motion + hover gating; tune to match the component's personality.
90
+
91
+ ## Required Output Format
92
+
93
+ Two parts, in this order.
94
+
95
+ ### Part 1 — Findings table (REQUIRED)
96
+
97
+ A single markdown table. One row per issue. Never a "Before:/After:" list.
98
+
99
+ | Before | After | Why |
100
+ | --- | --- | --- |
101
+ | `transition: all 300ms` | `transition: transform 200ms ease-out` | Specify exact properties; `all` animates unintended properties off-GPU |
102
+ | `transform: scale(0)` | `transform: scale(0.95); opacity: 0` | Nothing appears from nothing — `scale(0)` looks like it came from nowhere |
103
+ | `ease-in` on dropdown | `ease-out` + custom curve | `ease-in` delays the moment the user watches most; feels sluggish |
104
+ | `transform-origin: center` on popover | `var(--transform-origin)` (Base UI) | Popovers scale from their trigger, not center (modals are exempt) |
105
+
106
+ ### Part 2 — Verdict (REQUIRED)
107
+
108
+ Group remaining commentary by impact tier, highest first. Omit empty tiers.
109
+
110
+ 1. **Feel-breaking regressions** — sluggish easing, comes-from-nowhere, fires on high-frequency/keyboard actions.
111
+ 2. **Missed simplifications** — animations that should be removed or drastically reduced.
112
+ 3. **Performance** — non-GPU properties, dropped-frame risks, recalc storms.
113
+ 4. **Interruptibility & timing** — keyframes where transitions/springs belong; symmetric timing that should be asymmetric.
114
+ 5. **Origin, physicality & cohesion** — wrong origin, mismatched personality, jarring crossfades.
115
+ 6. **Accessibility** — reduced-motion and pointer/hover gating.
116
+
117
+ Close with an explicit decision:
118
+
119
+ - **Block** — any feel-breaking regression, animation on a keyboard/high-frequency action, `scale(0)`/`ease-in` on UI, or a non-GPU animation with an easy GPU fix.
120
+ - **Approve** — no feel-breaking regressions, no obvious motion that should be deleted, durations and easing within bounds, interruptibility handled where needed, reduced-motion respected.
121
+
122
+ Be specific and cite `file:line`. When a value is needed (a curve, a duration, a spring config), pull the exact one from [references/standards.md](references/standards.md) rather than approximating.
123
+
124
+ ## Guidelines
125
+
126
+ - Prefer CSS transitions/`@starting-style`/WAAPI for predetermined motion; JS/springs for dynamic, interruptible, gesture-driven motion.
127
+ - When unsure whether motion feels right, recommend reviewing it in slow motion / frame-by-frame and with fresh eyes the next day rather than guessing.
128
+
129
+ ---
130
+
131
+ ## Credit
132
+
133
+ Adapted for the rsc catalog from **[emilkowalski/skills](https://github.com/emilkowalski/skills)**
134
+ (MIT, commit `d23d7f8`) by Emil Kowalski — the design-engineering and animation philosophy behind
135
+ [Sonner](https://sonner.emilkowal.ski), [Vaul](https://vaul.emilkowal.ski) and
136
+ [animations.dev](https://animations.dev). The craft bar here is his; the routing, gates and evals
137
+ that make it an rsc skill are not. Nothing in this body is a substitute for his course.