Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions .claude/skills/add-animation/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,34 @@ The animation catalog is the **single source of truth**. A component may only us
`cubic-bezier(…)`. (`validate-tokens` blocks all of those; `validate-spec-compliance`
blocks an `animate-*` not in the catalog.)

## First: is this a catalog animation at all?

A keyframe is a **fixed** journey — it knows both ends before it runs. Three common needs are not
that, and adding a keyframe for them produces an animation that is wrong on every instance but the
one it was authored against. Do not add a catalog entry for these; use the recipe instead
(`transition-*` + `duration-*` / `ease-*` tokens, no new `animate-*`, no `@keyframes`):

| Need | Why it cannot be a keyframe | Recipe |
| --- | --- | --- |
| A box that resizes because its content changed (form step swap, panel level change, disclosure between two real heights) | Both heights are runtime facts; `auto` is not interpolable and must be the resting value | Measure → mutate → measure while still `auto` → pin the old value → **two** `requestAnimationFrame`s → set the new one → release to `auto` on `transitionend` **and** a fallback timer. `lib/animate-height.js` in the sample app is the working form. |
| A value that grows into place inside an element that keeps its size otherwise (a chip gaining its filter value) | The target width is the content's, unknown at author time | Single-column grid, `transition-[grid-template-columns]` from `grid-cols-[0fr]` to `grid-cols-[1fr]`; keep the clip bare and put padding on the row inside it |
| A composition assembling on first paint, its parts staggered from opposite edges | Per-instance offsets, direction, and a lead/follow delay — a keyframe would fix all three | `translate`/`opacity` on `data-[entered]:` variants, timing inline from the `duration` / `curve` tokens, flipped after two `requestAnimationFrame`s. `lib/auth-entrance.js` in the sample app is the working form. |

`animate-slide-down` (0 → auto disclosure) is the one *catalogued* size animation and stays that
way — it does not generalise to the incremental resizes above.

Two failure modes to check for before blaming the catalog, because both compile, lint, type-check
and animate **nothing**:

- **`transition-[transform]` beside a `translate-x-*` or `scale-*`.** Tailwind v4 compiles those to
the standalone `translate` / `scale` properties. Name those; a `<TransitionGroup>` `move-class`
names `transform,translate,scale,opacity` because its own move uses an inline `transform`.
- **A single `requestAnimationFrame` before flipping to the end state.** It can land in the frame
the browser is already painting, so the start state is never committed and the move snaps.

The consumer-facing form of all of this — including the full glitch catalog — is the shipped
`webkit-motion-polish` skill. Keep the two in step: a recipe added here is added there.

## Inputs

- `<name>` — kebab-case animation name (e.g. `slide-left`). Utility: `.animate-<name>`.
Expand Down Expand Up @@ -69,8 +97,16 @@ In the component, the motion-bearing class must pair with `motion-reduce:*` on t
class string (e.g. `animate-<name> motion-reduce:animate-none`), and the spec's
`## Motion & Animations` table must list `animate-<name>` + its reduced-motion fallback.

### 5. Verify by measuring

Sample the animated property across `requestAnimationFrame` and assert there are interpolated frames
between the start and end values. A snap and a 150ms ease are indistinguishable by eye, and every
silent failure above renders a correct final state — a screenshot proves nothing.

## Do not

- Do **not** add a keyframe for a journey whose endpoints are only known at runtime (see
**First: is this a catalog animation at all?**).
- Do **not** add a component-local `@keyframes` or `animate-[…]` / hardcoded timing.
- Do **not** edit `.claude/docs/DESIGN.md` (human mirror; updated separately).
- Do **not** invent a timing outside the `duration` / `curve` / `ease` token sets.
2 changes: 1 addition & 1 deletion packages/webkit/cli-templates/claude/CLAUDE.fragment.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ runs axe + screenshots), and taste is a review concern.
Start with `/webkit-ui-craft` (the umbrella that routes the rest). Get structure and states right
before any polish:

- **Structure:** `/webkit-ux-heuristics` (right component per moment) · `/webkit-ui-states` (state surface + async behavior) · `/webkit-form` · `/webkit-tables` (data-driven `<Table>` + cell recipes) · `/webkit-navigation`
- **Structure:** `/webkit-ux-heuristics` (right component per moment) · `/webkit-ui-states` (state surface + async behavior) · `/webkit-form` · `/webkit-tables` (data-driven `<Table>` + cell recipes) · `/webkit-lists` (the index page around it + the filter system) · `/webkit-navigation`
- **Foundation:** `/webkit-baseline-ui` (tokens, hierarchy, containers, responsive widths)
- **Cross-cutting quality:** `/webkit-theming-dark-mode` · `/webkit-data-viz`
- **Polish:** `/webkit-motion-polish` · `/webkit-impeccable-polish` (finish + earned delight)
Expand Down
Loading
Loading