feat(skills): add design-motion-principles from kylezantos
Motion/animation design skill with 3-designer lens (Emil Kowalski, Jakub Krehel, Jhey Tompkins). Two modes: Create and Audit. Complements frontend-design (broad) with deep motion expertise. Integration points: - skills-external/design-motion-principles/ — skill files + references - link.sh EXTERNAL_SKILLS — symlink to ~/.claude/skills/ - install-plugins.sh step 8c — presence check - update-all.sh step 7.2 — sync from GitHub - profiles: design, full, web, web-full — listed as external - plugin-advisor — decision table, recommended sets, conditional rules - design-gate — symlink check + non-blocking warning if missing Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
6b55a3aaf6
commit
469c807c10
@ -217,10 +217,10 @@ findings before producing recommendations:
|
|||||||
|
|
||||||
| Signal | Enable / Use | Disable / Skip | Notes |
|
| Signal | Enable / Use | Disable / Skip | Notes |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| `frontend` | ui-ux-pro-max, frontend-design | — | UI design and polish. frontend-design = Anthropic's anti-AI-slop design skill (external, symlinked) |
|
| `frontend` | ui-ux-pro-max, frontend-design, design-motion-principles | — | UI design + polish + motion. frontend-design = anti-AI-slop, design-motion-principles = motion/animation (both external, symlinked) |
|
||||||
| `mobile` (React Native/Expo/Flutter) | — | gstack (no browser QA), Docker N/A | ui-ux-pro-max optional |
|
| `mobile` (React Native/Expo/Flutter) | — | gstack (no browser QA), Docker N/A | ui-ux-pro-max optional |
|
||||||
| `monorepo` | per-package plugin recommendations | avoid recommending gstack for whole repo if only one package has browser QA | Specify which plugin applies to which package |
|
| `monorepo` | per-package plugin recommendations | avoid recommending gstack for whole repo if only one package has browser QA | Specify which plugin applies to which package |
|
||||||
| `design-system` | ui-ux-pro-max, frontend-design | — | Design tokens, theme, Storybook |
|
| `design-system` | ui-ux-pro-max, frontend-design, design-motion-principles | — | Design tokens, theme, Storybook, motion |
|
||||||
| `deploy` + `browser-qa` | gstack | — | Full-product workflow |
|
| `deploy` + `browser-qa` | gstack | — | Full-product workflow |
|
||||||
| `multi-session` | gsd v2 CLI | — | Run `gsd` in terminal, not CC plugin |
|
| `multi-session` | gsd v2 CLI | — | Run `gsd` in terminal, not CC plugin |
|
||||||
| `fast-libs` | context7 | — | Doc freshness critical |
|
| `fast-libs` | context7 | — | Doc freshness critical |
|
||||||
@ -271,12 +271,12 @@ When the plugin-advisor detects a `simple` or `hotfix` signal, suggest the appro
|
|||||||
| Project type | Plugins ON | OFF | Passive cost |
|
| Project type | Plugins ON | OFF | Passive cost |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| Backend API / microservice | superpowers, context7 (if fast libs) | ui-ux-pro-max, gstack | ~800t |
|
| Backend API / microservice | superpowers, context7 (if fast libs) | ui-ux-pro-max, gstack | ~800t |
|
||||||
| Frontend SPA / SSR | superpowers, ui-ux-pro-max, frontend-design, context7 | gstack | ~1400t |
|
| Frontend SPA / SSR | superpowers, ui-ux-pro-max, frontend-design, design-motion-principles, context7 | gstack | ~1400t |
|
||||||
| Full-stack SaaS | superpowers, gstack, ui-ux-pro-max, frontend-design, context7 | — | ~4200t |
|
| Full-stack SaaS | superpowers, gstack, ui-ux-pro-max, frontend-design, design-motion-principles, context7 | — | ~4200t |
|
||||||
| CLI tool / library | superpowers | all toggles | ~800t |
|
| CLI tool / library | superpowers | all toggles | ~800t |
|
||||||
| Multi-session large feature | superpowers + gsd v2 CLI (external) | — | ~800t CC |
|
| Multi-session large feature | superpowers + gsd v2 CLI (external) | — | ~800t CC |
|
||||||
| Quick fix / hotfix | superpowers | all toggles | ~800t |
|
| Quick fix / hotfix | superpowers | all toggles | ~800t |
|
||||||
| Design system / component lib | superpowers, ui-ux-pro-max, frontend-design | gstack, gsd | ~1200t |
|
| Design system / component lib | superpowers, ui-ux-pro-max, frontend-design, design-motion-principles | gstack, gsd | ~1200t |
|
||||||
| Fast-evolving libs (Next.js etc.) | superpowers, context7 | — | ~1000t |
|
| Fast-evolving libs (Next.js etc.) | superpowers, context7 | — | ~1000t |
|
||||||
| Enterprise multi-agent orchestration | superpowers + gsd v2 (external) | plugin-dev | ~800t CC |
|
| Enterprise multi-agent orchestration | superpowers + gsd v2 (external) | plugin-dev | ~800t CC |
|
||||||
|
|
||||||
@ -300,6 +300,7 @@ RULE: IF "monorepo" signal detected:
|
|||||||
RULE: IF "frontend" signal OR .tsx/.jsx count > 0:
|
RULE: IF "frontend" signal OR .tsx/.jsx count > 0:
|
||||||
→ ui-ux-pro-max ON if "design-system" signal (~400t)
|
→ ui-ux-pro-max ON if "design-system" signal (~400t)
|
||||||
→ frontend-design ON (external skill, 0t passive — symlink at ~/.claude/skills/frontend-design)
|
→ frontend-design ON (external skill, 0t passive — symlink at ~/.claude/skills/frontend-design)
|
||||||
|
→ design-motion-principles ON if anim-lib-installed or description mentions animation/motion (external, 0t passive)
|
||||||
|
|
||||||
RULE: IF "deploy" AND "browser-qa" signals:
|
RULE: IF "deploy" AND "browser-qa" signals:
|
||||||
→ gstack ON (~2750t) — full-product workflow
|
→ gstack ON (~2750t) — full-product workflow
|
||||||
@ -340,7 +341,8 @@ RULE: IF `browser-qa` signal (e2e tests, Playwright/Cypress/Puppeteer in deps):
|
|||||||
RULE: IF `design-system` signal (tokens, theme files, Storybook present):
|
RULE: IF `design-system` signal (tokens, theme files, Storybook present):
|
||||||
→ ui-ux-pro-max ON (~400t)
|
→ ui-ux-pro-max ON (~400t)
|
||||||
→ frontend-design ON (external skill, 0t passive)
|
→ frontend-design ON (external skill, 0t passive)
|
||||||
→ WARN if both OFF with this signal: significant design gap
|
→ design-motion-principles ON (external skill, 0t passive)
|
||||||
|
→ WARN if all three OFF with this signal: significant design gap
|
||||||
|
|
||||||
RULE: IF `complex-arch` signal (multiple services, event bus, distributed system):
|
RULE: IF `complex-arch` signal (multiple services, event bus, distributed system):
|
||||||
→ gsd v2 CLI recommended for multi-session coordination
|
→ gsd v2 CLI recommended for multi-session coordination
|
||||||
|
|||||||
@ -620,6 +620,22 @@ else
|
|||||||
fi
|
fi
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
|
# ── Step 8c: Design Motion Principles (kylezantos) ─────────
|
||||||
|
echo "── Step 8c: Design Motion Principles ─────────────────────"
|
||||||
|
echo ""
|
||||||
|
DMP_DIR="$REPO/skills-external/design-motion-principles"
|
||||||
|
if [ -f "$DMP_DIR/SKILL.md" ]; then
|
||||||
|
ok "design-motion-principles already present"
|
||||||
|
else
|
||||||
|
warn "design-motion-principles: not installed — clone from https://github.com/kylezantos/design-motion-principles"
|
||||||
|
fi
|
||||||
|
if [ -L "$HOME/.claude/skills/design-motion-principles" ]; then
|
||||||
|
ok "design-motion-principles symlink OK"
|
||||||
|
else
|
||||||
|
info "Symlinking — will be created by link.sh"
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# STEP 8.5 — EXTERNAL SKILLS (npx skills add …)
|
# STEP 8.5 — EXTERNAL SKILLS (npx skills add …)
|
||||||
# ============================================================
|
# ============================================================
|
||||||
@ -763,6 +779,7 @@ echo " 🔄 context7 CLI — ctx7 (npm global, standalone or MCP setup
|
|||||||
echo " 🔄 graphifyy — codebase knowledge graph (pipx, PreToolUse hook)"
|
echo " 🔄 graphifyy — codebase knowledge graph (pipx, PreToolUse hook)"
|
||||||
echo " 🔄 emil-design-eng — UI polish, animations, component craft (curl → symlink)"
|
echo " 🔄 emil-design-eng — UI polish, animations, component craft (curl → symlink)"
|
||||||
echo " 🔄 frontend-design — distinctive frontend interfaces, anti-AI-slop (anthropic-agent-skills)"
|
echo " 🔄 frontend-design — distinctive frontend interfaces, anti-AI-slop (anthropic-agent-skills)"
|
||||||
|
echo " 🔄 design-motion-principles — motion/animation design, 3-designer lens (kylezantos)"
|
||||||
echo " 🔄 darwin-skill — autonomous skill optimizer (npx skills, ~/.agents/skills/)"
|
echo " 🔄 darwin-skill — autonomous skill optimizer (npx skills, ~/.agents/skills/)"
|
||||||
echo " 🔄 find-skills — skill discovery helper (npx skills, ~/.agents/skills/)"
|
echo " 🔄 find-skills — skill discovery helper (npx skills, ~/.agents/skills/)"
|
||||||
echo " 🔄 magic MCP — 21st-dev UI generation MCP (toggle: lib/toggle-external.sh enable magic)"
|
echo " 🔄 magic MCP — 21st-dev UI generation MCP (toggle: lib/toggle-external.sh enable magic)"
|
||||||
@ -771,6 +788,7 @@ echo " All plugins installed at: user scope (~/.claude/plugins/)"
|
|||||||
echo " GStack skills symlinked individually into ~/.claude/skills/ (→ submodule)"
|
echo " GStack skills symlinked individually into ~/.claude/skills/ (→ submodule)"
|
||||||
echo " Emil Design Eng at: ~/.claude/skills/emil-design-eng/ (symlink → skills-external)"
|
echo " Emil Design Eng at: ~/.claude/skills/emil-design-eng/ (symlink → skills-external)"
|
||||||
echo " Frontend Design at: ~/.claude/skills/frontend-design/ (symlink → skills-external)"
|
echo " Frontend Design at: ~/.claude/skills/frontend-design/ (symlink → skills-external)"
|
||||||
|
echo " Design Motion Principles at: ~/.claude/skills/design-motion-principles/ (symlink → skills-external)"
|
||||||
echo " npx skills at: ~/.agents/skills/ (symlinked into ~/.claude/skills/)"
|
echo " npx skills at: ~/.agents/skills/ (symlinked into ~/.claude/skills/)"
|
||||||
echo ""
|
echo ""
|
||||||
echo " → Restart Claude Code — plugins load automatically"
|
echo " → Restart Claude Code — plugins load automatically"
|
||||||
|
|||||||
@ -27,14 +27,15 @@ Check BOTH the task description AND the filesystem:
|
|||||||
|
|
||||||
If **at least one signal** is detected:
|
If **at least one signal** is detected:
|
||||||
|
|
||||||
1. Check if `ui-ux-pro-max` and `frontend-design` are active:
|
1. Check if `ui-ux-pro-max`, `frontend-design`, and `design-motion-principles` are active:
|
||||||
```bash
|
```bash
|
||||||
source "$HOME/.claude/lib/detect-plugins.sh"
|
source "$HOME/.claude/lib/detect-plugins.sh"
|
||||||
detect_uiux_pro_max && echo "ui-ux-pro-max: ACTIVE" || echo "ui-ux-pro-max: INACTIVE"
|
detect_uiux_pro_max && echo "ui-ux-pro-max: ACTIVE" || echo "ui-ux-pro-max: INACTIVE"
|
||||||
[ -L "$HOME/.claude/skills/frontend-design" ] && echo "frontend-design: ACTIVE" || echo "frontend-design: INACTIVE"
|
[ -L "$HOME/.claude/skills/frontend-design" ] && echo "frontend-design: ACTIVE" || echo "frontend-design: INACTIVE"
|
||||||
|
[ -L "$HOME/.claude/skills/design-motion-principles" ] && echo "design-motion-principles: ACTIVE" || echo "design-motion-principles: INACTIVE"
|
||||||
```
|
```
|
||||||
|
|
||||||
2. If **both ACTIVE** → proceed silently. Design context is fully available.
|
2. If **all three ACTIVE** → proceed silently. Design context is fully available.
|
||||||
|
|
||||||
3. If **ui-ux-pro-max INACTIVE** → ask the user:
|
3. If **ui-ux-pro-max INACTIVE** → ask the user:
|
||||||
```
|
```
|
||||||
@ -51,6 +52,12 @@ If **at least one signal** is detected:
|
|||||||
Install: run install-plugins.sh or symlink skills-external/frontend-design to ~/.claude/skills/frontend-design
|
Install: run install-plugins.sh or symlink skills-external/frontend-design to ~/.claude/skills/frontend-design
|
||||||
```
|
```
|
||||||
|
|
||||||
|
5. If **design-motion-principles INACTIVE** and task mentions animation/motion/transition → warn (non-blocking):
|
||||||
|
```
|
||||||
|
ℹ️ design-motion-principles skill not installed — motion design guidelines unavailable.
|
||||||
|
Install: run install-plugins.sh or symlink skills-external/design-motion-principles to ~/.claude/skills/design-motion-principles
|
||||||
|
```
|
||||||
|
|
||||||
## IMPORTANT
|
## IMPORTANT
|
||||||
|
|
||||||
- This gate adds ~5 seconds overhead. Worth it for design quality.
|
- This gate adds ~5 seconds overhead. Worth it for design quality.
|
||||||
|
|||||||
@ -20,6 +20,7 @@ plan-ceo-review
|
|||||||
# External: design skills
|
# External: design skills
|
||||||
emil-design-eng external
|
emil-design-eng external
|
||||||
frontend-design external
|
frontend-design external
|
||||||
|
design-motion-principles external
|
||||||
|
|
||||||
# Plugin (auto-toggle)
|
# Plugin (auto-toggle)
|
||||||
ui-ux-pro-max plugin@ui-ux-pro-max-skill
|
ui-ux-pro-max plugin@ui-ux-pro-max-skill
|
||||||
|
|||||||
@ -75,6 +75,7 @@ guard
|
|||||||
# === External + plugin + MCP =========================================
|
# === External + plugin + MCP =========================================
|
||||||
emil-design-eng external
|
emil-design-eng external
|
||||||
frontend-design external
|
frontend-design external
|
||||||
|
design-motion-principles external
|
||||||
ui-ux-pro-max plugin@ui-ux-pro-max-skill
|
ui-ux-pro-max plugin@ui-ux-pro-max-skill
|
||||||
pr-review-toolkit plugin@claude-code-plugins
|
pr-review-toolkit plugin@claude-code-plugins
|
||||||
magic mcp
|
magic mcp
|
||||||
|
|||||||
@ -46,6 +46,7 @@ qa-only
|
|||||||
# === External + plugin + MCP =========================================
|
# === External + plugin + MCP =========================================
|
||||||
emil-design-eng external
|
emil-design-eng external
|
||||||
frontend-design external
|
frontend-design external
|
||||||
|
design-motion-principles external
|
||||||
ui-ux-pro-max plugin@ui-ux-pro-max-skill
|
ui-ux-pro-max plugin@ui-ux-pro-max-skill
|
||||||
magic mcp
|
magic mcp
|
||||||
|
|
||||||
|
|||||||
@ -35,6 +35,7 @@ validate personal
|
|||||||
# External: design skills
|
# External: design skills
|
||||||
emil-design-eng external
|
emil-design-eng external
|
||||||
frontend-design external
|
frontend-design external
|
||||||
|
design-motion-principles external
|
||||||
|
|
||||||
# Plugin: UI/UX intelligence (auto-toggle)
|
# Plugin: UI/UX intelligence (auto-toggle)
|
||||||
ui-ux-pro-max plugin@ui-ux-pro-max-skill
|
ui-ux-pro-max plugin@ui-ux-pro-max-skill
|
||||||
|
|||||||
2
link.sh
2
link.sh
@ -50,7 +50,7 @@ if [ ! -d "$REPO/skills-external/gstack" ]; then
|
|||||||
echo "⚠️ GStack submodule not found — run: git submodule update --init"
|
echo "⚠️ GStack submodule not found — run: git submodule update --init"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
EXTERNAL_SKILLS=(emil-design-eng frontend-design)
|
EXTERNAL_SKILLS=(emil-design-eng frontend-design design-motion-principles)
|
||||||
for _ext_skill in "${EXTERNAL_SKILLS[@]}"; do
|
for _ext_skill in "${EXTERNAL_SKILLS[@]}"; do
|
||||||
if [ -d "$REPO/skills-external/$_ext_skill" ]; then
|
if [ -d "$REPO/skills-external/$_ext_skill" ]; then
|
||||||
if [ -L "$CLAUDE/skills/$_ext_skill" ] && [ "$(readlink "$CLAUDE/skills/$_ext_skill")" = "$REPO/skills-external/$_ext_skill" ]; then
|
if [ -L "$CLAUDE/skills/$_ext_skill" ] && [ "$(readlink "$CLAUDE/skills/$_ext_skill")" = "$REPO/skills-external/$_ext_skill" ]; then
|
||||||
|
|||||||
21
skills-external/design-motion-principles/LICENSE.txt
Normal file
21
skills-external/design-motion-principles/LICENSE.txt
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2026 Kyle Zantos
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
122
skills-external/design-motion-principles/SKILL.md
Normal file
122
skills-external/design-motion-principles/SKILL.md
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
---
|
||||||
|
name: design-motion-principles
|
||||||
|
description: Motion and interaction design expert based on Emil Kowalski, Jakub Krehel, and Jhey Tompkins' techniques. Two modes — build interactive components with purposeful motion, or audit existing animations to catch AI-slop motion patterns (audit emits a branded HTML report with looping demos). Use when creating, adding, animating, or reviewing UI motion: transitions, hover states, micro-interactions, enter/exit animations, or any motion design work in React, Framer Motion, CSS, or HTML. Provides per-designer perspectives with context-aware weighting.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Design Motion Principles
|
||||||
|
|
||||||
|
You are a senior design engineer specializing in motion and interaction design. This skill operates in two modes:
|
||||||
|
|
||||||
|
- **Create** — Build interactive components with purposeful motion → `workflows/create.md`
|
||||||
|
- **Audit** — Review existing motion design and report findings → `workflows/audit.md`
|
||||||
|
|
||||||
|
**Scope**: Web and app UI motion — HTML/CSS, React, Framer Motion / Motion, iOS/Android transitions, design system animations. The frequency framework still applies to other motion work (game engines, Lottie, Rive, video), but designer-specific techniques may not translate.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## STEP 0: Detect Mode (DO THIS FIRST)
|
||||||
|
|
||||||
|
| Signal in the request | Mode |
|
||||||
|
|-----------------------|------|
|
||||||
|
| "build", "create", "add animation", "animate this", "implement", "make it feel…" | **Create** |
|
||||||
|
| "audit", "review", "evaluate", "check", "feedback on", "is this motion good" | **Audit** |
|
||||||
|
| Ambiguous (e.g. "look at this modal animation") | Ask the user |
|
||||||
|
|
||||||
|
For ambiguous requests, if `AskUserQuestion` is available, present:
|
||||||
|
- **Create** — Build or improve the component's motion
|
||||||
|
- **Audit** — Review existing motion and report findings
|
||||||
|
|
||||||
|
Otherwise ask in plain text: "Should I build/improve the motion (Create mode), or review existing motion and report findings (Audit mode)?"
|
||||||
|
|
||||||
|
**Once the mode is known, read the matching workflow file and follow it exactly.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## The Three Designers
|
||||||
|
|
||||||
|
- **Emil Kowalski** (Linear, ex-Vercel) — Restraint, speed, purposeful motion. Best for productivity tools.
|
||||||
|
- **Jakub Krehel** (jakub.kr) — Subtle production polish, professional refinement. Best for shipped consumer apps.
|
||||||
|
- **Jhey Tompkins** (@jh3yy) — Playful experimentation, CSS innovation. Best for creative sites, kids apps, portfolios.
|
||||||
|
|
||||||
|
> These three lenses distill each designer's *publicly published* work — courses, articles, talks, and open-source projects. The weighting framework and the "lens" framing are this skill's interpretation of their principles, named in tribute; they are not authored or endorsed by the designers themselves.
|
||||||
|
|
||||||
|
Each designer answers a different question:
|
||||||
|
- **Emil** — *"Should this animate at all?"*
|
||||||
|
- **Jakub** — *"Is this subtle and polished enough for production?"*
|
||||||
|
- **Jhey** — *"What could this become?"*
|
||||||
|
|
||||||
|
**Critical insight**: These perspectives are context-dependent, not universal rules. A kids' app should prioritize Jakub + Jhey (polish + delight), not Emil's productivity-focused speed rules. Both modes weight the designers by project context before doing anything.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Context-to-Perspective Mapping
|
||||||
|
|
||||||
|
| Project Type | Primary | Secondary | Selective |
|
||||||
|
|--------------|---------|-----------|-----------|
|
||||||
|
| Productivity tool (Linear, Raycast) | Emil | Jakub | Jhey (onboarding only) |
|
||||||
|
| Kids app / Educational | Jakub | Jhey | Emil (high-freq game interactions) |
|
||||||
|
| Creative portfolio | Jakub | Jhey | Emil (high-freq interactions) |
|
||||||
|
| Marketing/landing page | Jakub | Jhey | Emil (forms, nav) |
|
||||||
|
| SaaS dashboard | Emil | Jakub | Jhey (empty states) |
|
||||||
|
| Mobile app | Jakub | Emil | Jhey (delighters) |
|
||||||
|
| E-commerce | Jakub | Emil | Jhey (product showcase) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Core Principles (Both Modes)
|
||||||
|
|
||||||
|
### The Frequency Gate
|
||||||
|
|
||||||
|
Before adding or approving any animation, ask how often the user triggers it:
|
||||||
|
|
||||||
|
| Frequency | Recommendation |
|
||||||
|
|-----------|----------------|
|
||||||
|
| Rare (monthly) | Delightful, expressive motion welcome |
|
||||||
|
| Occasional (daily) | Subtle, fast motion |
|
||||||
|
| Frequent (100s/day) | No animation or instant transition |
|
||||||
|
| Keyboard-initiated | Never animate |
|
||||||
|
|
||||||
|
### Duration Guidelines (Context-Dependent)
|
||||||
|
|
||||||
|
| Context | Guideline |
|
||||||
|
|---------|-----------|
|
||||||
|
| Productivity UI (Emil) | Under 300ms — 180ms ideal |
|
||||||
|
| Production polish (Jakub) | 200-500ms for smoothness |
|
||||||
|
| Creative/kids/playful (Jhey) | Whatever serves the effect |
|
||||||
|
|
||||||
|
**Do not universally flag or cap durations.** Check the context weighting first.
|
||||||
|
|
||||||
|
### The Golden Rule
|
||||||
|
|
||||||
|
> "The best animation is that which goes unnoticed."
|
||||||
|
|
||||||
|
If users comment "nice animation!" on every interaction, it's probably too prominent for production. (Exception: kids apps and playful contexts where delight IS the goal.)
|
||||||
|
|
||||||
|
### Accessibility is NOT Optional
|
||||||
|
|
||||||
|
Every animation — generated in Create mode or reviewed in Audit mode — must handle `prefers-reduced-motion`. No exceptions. See `references/accessibility.md`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Reference Index
|
||||||
|
|
||||||
|
| File | Contents | Load When |
|
||||||
|
|------|----------|-----------|
|
||||||
|
| [Motion Cookbook](references/motion-cookbook.md) | All motion recipes — enter/exit, easing, springs, clip-path, @property, FLIP, scroll-driven | Create mode (always); Audit mode for implementation recommendations |
|
||||||
|
| [Creation Gotchas](references/creation-gotchas.md) | Claude's failure modes when writing motion | Create mode (always) |
|
||||||
|
| [Audit Checklist](references/audit-checklist.md) | Systematic audit checklist | Audit mode (always) |
|
||||||
|
| [Anti-Checklist](references/anti-checklist.md) | Quality gate — AI-slop motion categories + anti-patterns to flag | Audit mode (always) |
|
||||||
|
| [Emil Kowalski](references/emil-kowalski.md) | Restraint philosophy, frequency rule, decision frameworks | Either mode, if Emil is weighted |
|
||||||
|
| [Jakub Krehel](references/jakub-krehel.md) | Production polish philosophy and decision frameworks | Either mode, if Jakub is weighted |
|
||||||
|
| [Jhey Tompkins](references/jhey-tompkins.md) | Playful experimentation philosophy and frameworks | Either mode, if Jhey is weighted |
|
||||||
|
| [Accessibility](references/accessibility.md) | prefers-reduced-motion, vestibular safety | Both modes (mandatory) |
|
||||||
|
| [Performance](references/performance.md) | GPU optimization, will-change, layout thrash | Either mode, for complex animations |
|
||||||
|
| [Output Format](references/output-format.md) | Audit report template — HTML mode (default) + terminal mode (flag) | Audit mode only |
|
||||||
|
| [Demo Shell](references/demo-shell.html) | Visual container template for per-finding demo cards in the HTML report | Audit mode, HTML output |
|
||||||
|
|
||||||
|
## Workflow Index
|
||||||
|
|
||||||
|
| Workflow | Purpose |
|
||||||
|
|----------|---------|
|
||||||
|
| [Create](workflows/create.md) | Build interactive components with purposeful motion |
|
||||||
|
| [Audit](workflows/audit.md) | Review existing motion design, produce a per-designer report |
|
||||||
@ -0,0 +1,52 @@
|
|||||||
|
# Accessibility
|
||||||
|
|
||||||
|
**This is not optional.** Motion can cause discomfort, nausea, or distraction for many users.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Respect User Preferences
|
||||||
|
|
||||||
|
```css
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
|
animation-duration: 0.01ms !important;
|
||||||
|
animation-iteration-count: 1 !important;
|
||||||
|
transition-duration: 0.01ms !important;
|
||||||
|
scroll-behavior: auto !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**What this does**: Effectively disables animations while preserving final states (so layouts don't break).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Functional vs. Decorative Motion
|
||||||
|
|
||||||
|
| Type | Purpose | Reduced Motion Behavior |
|
||||||
|
|------|---------|------------------------|
|
||||||
|
| **Functional** | Indicates state changes, spatial relationships, orientation | May need alternative (instant state change, no transition) |
|
||||||
|
| **Decorative** | Pure delight, visual interest | Can be fully removed |
|
||||||
|
|
||||||
|
**The test**: Does removing this animation break the user's ability to understand what happened? If yes, it's functional.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Motion Sensitivity Considerations
|
||||||
|
|
||||||
|
- Avoid large-scale motion (full-screen transitions, parallax)
|
||||||
|
- Avoid continuous or looping animations that can't be paused
|
||||||
|
- Provide pause controls for any ambient animation
|
||||||
|
- Be especially careful with vestibular triggers: zooming, spinning, parallax
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Implementation Checklist
|
||||||
|
|
||||||
|
- [ ] Tested with `prefers-reduced-motion: reduce` enabled
|
||||||
|
- [ ] No vestibular triggers (excessive zoom, spin, parallax)
|
||||||
|
- [ ] Looping animations can be paused
|
||||||
|
- [ ] Functional animations have non-motion alternatives
|
||||||
|
- [ ] Users can complete all tasks with animations disabled
|
||||||
@ -0,0 +1,274 @@
|
|||||||
|
# Anti-Checklist
|
||||||
|
|
||||||
|
This file is the audit's quality gate. The categories below describe motion patterns to **flag** in audited code — AI-slop tells at the top (where most 2026 motion problems live), followed by perspective-specific anti-patterns from Emil, Jakub, and Jhey, then general motion mistakes and code-shaped red flags. When audited code matches a pattern here, the audit surfaces a finding and the agent generates a per-finding motion suggestion by reading the relevant philosophy reference (`emil-kowalski.md`, `jakub-krehel.md`, `jhey-tompkins.md`).
|
||||||
|
|
||||||
|
The file frames patterns as "things to flag," not "mistakes to avoid" — language that makes the audit's adversarial posture explicit.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## AI-Slop Motion Patterns
|
||||||
|
|
||||||
|
These are the recognizable motion fingerprints of AI-generated UIs in 2026. They're not always wrong in isolation — what makes them slop is *frequency* and *uniformity*. Finding one instance is normal polish; finding the same pattern slapped across the codebase is the tell. Each category includes a flagging heuristic below the definition so the audit isn't tripped by single intentional uses.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Pulsing indicators
|
||||||
|
|
||||||
|
Glowing dots, breathing CTAs, throbbing rings, "live"/"online"/"recording"/"AI active" pulse animations, dark-mode pulse glows — any looped scale/opacity pulse used to draw attention to a status element.
|
||||||
|
|
||||||
|
**Flag when you see:**
|
||||||
|
- `@keyframes` rules with names containing `pulse`, `glow`, `breathe`, `throb`
|
||||||
|
- `animation: ... infinite` on small UI elements (dots, badges, status indicators)
|
||||||
|
- `box-shadow` or `opacity` loops on status icons
|
||||||
|
- Tailwind `animate-pulse` on indicator dots or active-state elements
|
||||||
|
|
||||||
|
**Heuristic:** Flag *any* instance. Pulsing indicators are almost always slop — the only exception is a single brand element with explicit design rationale stated in code comments or design docs.
|
||||||
|
|
||||||
|
**Fix lens:** Emil — purposeful restraint. See `references/emil-kowalski.md`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Blur-everywhere entrances
|
||||||
|
|
||||||
|
`filter: blur(Npx)` applied to every entering element on mount — sections, cards, images, paragraphs. Jakub's enter recipe (`opacity + translateY + blur`) is excellent in moderation; AI-slop versions apply it uniformly across the page.
|
||||||
|
|
||||||
|
**Flag when you see:**
|
||||||
|
- `initial={{ filter: 'blur(Npx)' }}` or `from { filter: blur(Npx); }` on multiple distinct components in the same view
|
||||||
|
- Identical blur values (e.g., `blur(4px)`) repeated across components without context distinction
|
||||||
|
- Blur on text-bearing entrances (headings, paragraphs) where it impairs first-paint readability
|
||||||
|
|
||||||
|
**Heuristic:** Flag when ≥3 distinct components in the same view share the same `filter: blur()` enter pattern. Single uses with intent (a hero element, a modal) are fine.
|
||||||
|
|
||||||
|
**Fix lens:** Jakub — production polish, but selective. See `references/jakub-krehel.md`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Hover-scale-on-everything
|
||||||
|
|
||||||
|
`transform: scale(1.0X)` on `:hover` applied to every card, button, and image without intent. The micro-bounce-on-hover feels polished in moderation; AI-slop versions slap it on indiscriminately.
|
||||||
|
|
||||||
|
**Flag when you see:**
|
||||||
|
- `transition` rules with `transform: scale(1.0X)` on `:hover` across multiple card/button/image components
|
||||||
|
- Identical scale values (e.g., `scale(1.05)`) repeated across selectors with no discriminating context
|
||||||
|
- Tailwind `hover:scale-105` applied to grids of repeated items
|
||||||
|
|
||||||
|
**Heuristic:** Flag when ≥3 distinct components share the same `transform: scale(1.0X)` on `:hover` with no other discriminating selector context. Single intentional uses (e.g., a primary CTA) are fine.
|
||||||
|
|
||||||
|
**Fix lens:** Emil for utility-shaped elements (none); Jakub for product-shaped elements (selective). See `references/emil-kowalski.md` and `references/jakub-krehel.md`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Stagger-spam-on-every-list
|
||||||
|
|
||||||
|
`stagger`, `staggerChildren`, or hand-rolled `animation-delay: calc(var(--i) * 50ms)` patterns applied to every list, grid, or repeated-element block. Jhey-style stagger on a deliberate moment is delightful; AI-slop spreads it across every list as default polish.
|
||||||
|
|
||||||
|
**Flag when you see:**
|
||||||
|
- `staggerChildren` in framer-motion `variants` across multiple list components
|
||||||
|
- `animation-delay: calc(...)` with item-index multipliers across multiple components
|
||||||
|
- Sequential delays applied to lists that don't read as a moment (search results, settings options, table rows)
|
||||||
|
|
||||||
|
**Heuristic:** Flag when ≥2 lists in the same view use stagger entrance. One intentional moment is fine; two or more is the tell.
|
||||||
|
|
||||||
|
**Fix lens:** Emil for utility lists (no stagger); Jhey for delight moments (selective). See `references/emil-kowalski.md` and `references/jhey-tompkins.md`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Bouncy-springs-on-utility-actions
|
||||||
|
|
||||||
|
`type: 'spring'` with bounce on dropdown opens, toggle switches, menu reveals, modal entrances — utility actions where bounce reads as "playful" but the action itself is productivity-oriented.
|
||||||
|
|
||||||
|
**Flag when you see:**
|
||||||
|
- `transition={{ type: 'spring', bounce: > 0 }}` on dropdowns, popovers, menus, toggles, modal opens, settings panels
|
||||||
|
- CSS `cubic-bezier(...)` with overshoot values on utility elements
|
||||||
|
- Identical spring configs across utility components
|
||||||
|
|
||||||
|
**Heuristic:** Flag *any* spring with bounce > 0 on a utility action (dropdown, menu, toggle, modal, settings panel). Bounce belongs on playful elements — celebration moments, kids apps, intentional delight — not productivity UI.
|
||||||
|
|
||||||
|
**Fix lens:** Emil — speed and purpose. See `references/emil-kowalski.md`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Uniform-fade-in-on-every-element
|
||||||
|
|
||||||
|
Identical `opacity + translateY` (with or without blur) enter animations applied to every section, card, paragraph, and heading. The "polished entrance" treatment from Jakub used uniformly across the page, regardless of element type or context.
|
||||||
|
|
||||||
|
**Flag when you see:**
|
||||||
|
- Multiple components sharing identical `initial`/`animate` opacity+translateY values
|
||||||
|
- `whileInView` with identical viewport options applied to every block on a page
|
||||||
|
- CSS keyframes with generic names (`fadeInUp`, `enter`, `reveal`) attached to many selectors
|
||||||
|
|
||||||
|
**Heuristic:** Flag when ≥4 distinct components share identical enter animations (same opacity, same translateY, same duration, same easing). Three is acceptable polish baseline; four is uniformity slop.
|
||||||
|
|
||||||
|
**Fix lens:** Jakub — selective polish with hierarchy. See `references/jakub-krehel.md`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Motion-on-mount-for-static-content
|
||||||
|
|
||||||
|
Entrance animations on headings, body paragraphs, navigation links, and other content that should appear instantly. The "fade in everything" pattern that delays reading and makes the page feel sluggish.
|
||||||
|
|
||||||
|
**Flag when you see:**
|
||||||
|
- `initial`/`animate` on `<h1>`, `<h2>`, `<p>`, `<nav>` elements
|
||||||
|
- `whileInView` on body copy (paragraphs, articles, prose)
|
||||||
|
- `animation` rules on text-only components without functional reason
|
||||||
|
|
||||||
|
**Heuristic:** Flag any motion on a text-only or navigation element when the motion's only purpose is the entrance itself. Carousels, sliders, and hero animations are fine when the motion serves a function (orientation, narrative pacing, attention direction).
|
||||||
|
|
||||||
|
**Fix lens:** Emil — animations should serve a purpose, not announce themselves. See `references/emil-kowalski.md`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## From Emil's Perspective (Purposeful Restraint)
|
||||||
|
|
||||||
|
- **Animating high-frequency interactions** — If users trigger this 100s of times daily, remove the animation
|
||||||
|
- **Animating keyboard-initiated actions** — Keyboard shortcuts should NEVER animate
|
||||||
|
- **Animations over 300ms** — UI animations should be under 300ms; 180ms feels more responsive than 400ms
|
||||||
|
- **Animating from scale(0)** — Start from `scale(0.9)` or higher for natural motion
|
||||||
|
- **Same tooltip behavior everywhere** — First tooltip: delayed + animated. Subsequent: instant
|
||||||
|
- **Using default CSS easing** — Built-in `ease` and `ease-in-out` lack strength; use custom curves
|
||||||
|
- **Ignoring transform-origin** — Dropdowns should expand from their trigger, not center
|
||||||
|
- **Expecting delight in productivity tools** — Users of high-frequency tools prioritize speed over delight
|
||||||
|
- **Using keyframes for interruptible animations** — Keyframes can't retarget mid-flight; use CSS transitions with state
|
||||||
|
- **CSS variables for frequent updates** — Causes expensive style recalculation; update styles directly on element
|
||||||
|
- **Distance thresholds for dismissal** — Use velocity (distance/time) instead; fast short gestures should work
|
||||||
|
- **Abrupt boundary stops** — Use damping; things slow down before stopping in real life
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## From Jakub's Perspective (Production Polish)
|
||||||
|
|
||||||
|
- **Making enter and exit animations equally prominent** — Exits should be subtler
|
||||||
|
- **Using solid borders when shadows would adapt better** — Especially on varied backgrounds
|
||||||
|
- **Forgetting optical alignment** — Buttons with icons, play buttons, asymmetric shapes
|
||||||
|
- **Over-animating** — If users notice the animation itself, it's too much
|
||||||
|
- **Using the same animation everywhere** — Context should drive timing and easing choices
|
||||||
|
- **Ignoring hover state transitions** — Even small transitions (150-200ms) feel more polished than instant changes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## From Jhey's Perspective (Creative Learning)
|
||||||
|
|
||||||
|
- **Filtering ideas based on "usefulness" too early** — Make first, judge later
|
||||||
|
- **Not documenting random creative sparks** — Keep notebooks everywhere, including by your bed
|
||||||
|
- **Thinking CSS art is useless** — It teaches real skills (clip-path, layering, complex shapes)
|
||||||
|
- **Focusing on "How do I learn X?" instead of "How do I make Y?"** — Let ideas drive learning
|
||||||
|
- **Following tutorials without experimenting** — Tutorials teach techniques; experimentation teaches problem-solving
|
||||||
|
- **Giving up when something doesn't work** — The struggle is where learning happens
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## General Motion Design Mistakes
|
||||||
|
|
||||||
|
- **Animating layout-triggering properties** (width, height, top, left) — Use transform instead
|
||||||
|
- **No animation at all** — Instant state changes feel broken to modern users
|
||||||
|
- **Same duration for all animations** — Smaller elements should animate faster
|
||||||
|
- **Forgetting `prefers-reduced-motion`** — Not optional
|
||||||
|
|
||||||
|
*Note: Duration is designer-dependent. Emil prefers under 300ms for productivity tools. Jakub and Jhey may use longer durations when polish or effect warrants it.*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Red Flags in Code Review
|
||||||
|
|
||||||
|
Watch for these patterns:
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
// BAD: Animating layout properties
|
||||||
|
animate={{ width: 200, height: 100 }}
|
||||||
|
|
||||||
|
// GOOD: Use transform
|
||||||
|
animate={{ scale: 1.2 }}
|
||||||
|
```
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
// BAD: Same animation for enter and exit
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
exit={{ opacity: 0, y: 20 }}
|
||||||
|
|
||||||
|
// GOOD: Subtler exit
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
exit={{ opacity: 0, y: -8 }}
|
||||||
|
```
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* BAD: No reduced motion support */
|
||||||
|
.animated { animation: bounce 1s infinite; }
|
||||||
|
|
||||||
|
/* GOOD: Respects user preference */
|
||||||
|
@media (prefers-reduced-motion: no-preference) {
|
||||||
|
.animated { animation: bounce 1s infinite; }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* BAD: will-change everywhere */
|
||||||
|
* { will-change: transform; }
|
||||||
|
|
||||||
|
/* GOOD: Targeted will-change */
|
||||||
|
.animated-button { will-change: transform, opacity; }
|
||||||
|
```
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
// BAD: Animating from scale(0) (Emil)
|
||||||
|
initial={{ scale: 0 }}
|
||||||
|
animate={{ scale: 1 }}
|
||||||
|
|
||||||
|
// GOOD: Start from higher scale
|
||||||
|
initial={{ scale: 0.9, opacity: 0 }}
|
||||||
|
animate={{ scale: 1, opacity: 1 }}
|
||||||
|
```
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
// Per Emil: Too slow for productivity UI
|
||||||
|
transition={{ duration: 0.4 }}
|
||||||
|
|
||||||
|
// Per Emil: Fast, snappy (but Jakub/Jhey might use 0.4 for polish)
|
||||||
|
transition={{ duration: 0.18 }}
|
||||||
|
```
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* BAD: Dropdown expanding from center (Emil) */
|
||||||
|
.dropdown {
|
||||||
|
transform-origin: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* GOOD: Origin-aware animation */
|
||||||
|
.dropdown {
|
||||||
|
transform-origin: top center;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* BAD: Keyframes can't be interrupted (Emil) */
|
||||||
|
@keyframes slideIn {
|
||||||
|
from { transform: translateY(100%); }
|
||||||
|
to { transform: translateY(0); }
|
||||||
|
}
|
||||||
|
.toast { animation: slideIn 400ms ease; }
|
||||||
|
|
||||||
|
/* GOOD: Transitions can retarget mid-flight */
|
||||||
|
.toast {
|
||||||
|
transform: translateY(100%);
|
||||||
|
transition: transform 400ms ease;
|
||||||
|
}
|
||||||
|
.toast.mounted {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// BAD: CSS variables cause cascade recalc (Emil)
|
||||||
|
element.style.setProperty('--drag-y', `${y}px`);
|
||||||
|
|
||||||
|
// GOOD: Direct style update
|
||||||
|
element.style.transform = `translateY(${y}px)`;
|
||||||
|
```
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// BAD: Distance threshold for dismissal (Emil)
|
||||||
|
if (dragDistance > 100) dismiss();
|
||||||
|
|
||||||
|
// GOOD: Velocity-based (fast short gestures work)
|
||||||
|
const velocity = dragDistance / elapsedTime;
|
||||||
|
if (velocity > 0.11) dismiss();
|
||||||
|
```
|
||||||
@ -0,0 +1,137 @@
|
|||||||
|
# Audit Checklist
|
||||||
|
|
||||||
|
Use this checklist when reviewing motion design in any UI code.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Philosophy Check (Do First)
|
||||||
|
|
||||||
|
- [ ] **How often will users trigger this?** (Frequent = less/no animation — Emil's rule)
|
||||||
|
- [ ] **Is this keyboard-initiated?** (If yes, don't animate — Emil's rule)
|
||||||
|
- [ ] **Does this animation serve a purpose?** (orientation, feedback, continuity—not just decoration)
|
||||||
|
- [ ] **Will users notice this animation consciously?** (If yes for production UI, probably too much)
|
||||||
|
- [ ] **Have I tested this with `prefers-reduced-motion: reduce`?**
|
||||||
|
- [ ] **Does this feel natural after the 10th interaction?** (Test repeatedly, not just once)
|
||||||
|
- [ ] **Is the easing appropriate for my brand/context?**
|
||||||
|
- [ ] **Is the duration appropriate for context?** (Emil prefers under 300ms; Jakub/Jhey may use longer for polish or effect)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Motion Gap Analysis (Check BEFORE Reviewing Existing Animations)
|
||||||
|
|
||||||
|
Conditional UI changes that **lack** animation are often worse than poorly-tuned animations:
|
||||||
|
|
||||||
|
- [ ] **Searched for conditional renders** — `{condition && <Component />}` patterns
|
||||||
|
- [ ] **Searched for ternary swaps** — `{condition ? <A /> : <B />}` patterns
|
||||||
|
- [ ] **Searched for dynamic inline styles** — `style={{ prop: dynamicValue }}` without transition
|
||||||
|
- [ ] **Each conditional render** either has AnimatePresence wrapper OR doesn't need animation (static content)
|
||||||
|
- [ ] **Mode switches** (tabs, toggles) animate their content changes, not just the switch itself
|
||||||
|
- [ ] **Settings panels** with conditional controls have enter/exit animations
|
||||||
|
- [ ] **Expandable sections** animate height, not just show/hide
|
||||||
|
- [ ] **Loading → Content** transitions are smooth, not instant swaps
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Enter/Exit States
|
||||||
|
|
||||||
|
- [ ] Enter animations combine opacity + translateY + blur
|
||||||
|
- [ ] Exit animations are subtler than enters (smaller translateY, same blur/opacity)
|
||||||
|
- [ ] `animation-fill-mode: backwards` used for delayed sequences
|
||||||
|
- [ ] Elements don't flash before their delayed animation starts
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Easing & Timing
|
||||||
|
|
||||||
|
- [ ] Appropriate easing for context (not default `ease` everywhere)
|
||||||
|
- [ ] Custom Bézier curves used instead of built-in easing (Emil's rule)
|
||||||
|
- [ ] Spring animations for interactive elements
|
||||||
|
- [ ] Durations appropriate for context (Emil: under 300ms; others: whatever serves the design)
|
||||||
|
- [ ] Consistent timing values across related animations
|
||||||
|
- [ ] Transform-origin matches interaction source (dropdowns from trigger)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Visual Polish
|
||||||
|
|
||||||
|
- [ ] Shadows instead of borders where background varies
|
||||||
|
- [ ] Gradients using oklch color space for smooth blending
|
||||||
|
- [ ] Blur used intentionally as a state signal
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Optical Alignment
|
||||||
|
|
||||||
|
- [ ] Buttons with icons have adjusted padding
|
||||||
|
- [ ] Asymmetric icons (play, arrows) are visually centered
|
||||||
|
- [ ] Text and icons feel balanced
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## State Transitions
|
||||||
|
|
||||||
|
- [ ] Icon swaps are animated (opacity, scale, blur)
|
||||||
|
- [ ] Loading states have smooth transitions
|
||||||
|
- [ ] Hover states have transitions (150-200ms minimum)
|
||||||
|
- [ ] Button press has scale feedback (`scale(0.97)` on `:active`)
|
||||||
|
- [ ] Elements don't animate from `scale(0)` (use `0.9+` instead)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Interaction Patterns (Emil's Rules)
|
||||||
|
|
||||||
|
- [ ] Tooltips: first delayed + animated, subsequent instant
|
||||||
|
- [ ] Animations are interruptible (can change mid-animation)
|
||||||
|
- [ ] Clip-path used for reveals instead of width/height
|
||||||
|
- [ ] High-frequency actions have minimal or no animation
|
||||||
|
- [ ] Keyboard shortcuts don't animate
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Performance
|
||||||
|
|
||||||
|
- [ ] `will-change` used sparingly and specifically
|
||||||
|
- [ ] Animations use transform/opacity (not layout properties)
|
||||||
|
- [ ] Tested on low-end devices
|
||||||
|
- [ ] No continuous animations without purpose
|
||||||
|
- [ ] CSS transitions (not keyframes) for interruptible animations (Emil)
|
||||||
|
- [ ] Direct style updates for drag operations (not CSS variables) (Emil)
|
||||||
|
- [ ] Velocity-based thresholds (not distance) for swipe dismiss (Emil)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Accessibility
|
||||||
|
|
||||||
|
- [ ] Respects `prefers-reduced-motion`
|
||||||
|
- [ ] No vestibular triggers (excessive zoom, spin, parallax)
|
||||||
|
- [ ] Looping animations can be paused
|
||||||
|
- [ ] Functional animations have non-motion alternatives
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quick Reference: Severity Levels
|
||||||
|
|
||||||
|
**Critical (Must Fix)**:
|
||||||
|
- Missing `prefers-reduced-motion` support
|
||||||
|
- Animating layout properties (width, height, top, left)
|
||||||
|
- No exit animations (elements just disappear)
|
||||||
|
- **Motion gaps in primary UI** — Conditional controls/panels that snap in/out without animation
|
||||||
|
- Animating keyboard-initiated actions (Emil)
|
||||||
|
- Animations on high-frequency actions (100s/day)
|
||||||
|
|
||||||
|
**Important (Should Fix)**:
|
||||||
|
- Exit animations as prominent as enter animations
|
||||||
|
- Missing blur in enter animations
|
||||||
|
- Animating from `scale(0)` instead of `0.9+` (Emil)
|
||||||
|
- Default CSS easing instead of custom curves (Emil)
|
||||||
|
- Wrong transform-origin on dropdowns/popovers (Emil)
|
||||||
|
|
||||||
|
**Context-Dependent (Check Against Designer Perspective)**:
|
||||||
|
- Durations over 300ms (Emil flags this; Jakub/Jhey may approve for polish)
|
||||||
|
|
||||||
|
**Nice to Have**:
|
||||||
|
- Optical alignment refinements
|
||||||
|
- oklch color space for gradients
|
||||||
|
- Spring animations instead of ease
|
||||||
|
- Button scale feedback on press
|
||||||
|
- Tooltip delay pattern (first delayed, subsequent instant)
|
||||||
@ -0,0 +1,36 @@
|
|||||||
|
# Creation Gotchas
|
||||||
|
|
||||||
|
Where Claude typically fails when **writing** motion (as opposed to auditing it). Self-check generated code against every item before presenting.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Motion that shouldn't exist
|
||||||
|
|
||||||
|
- **Don't animate just because you can.** Decorative motion added "for polish" is the default failure mode. Every animation needs a purpose — feedback, orientation, or continuity. If you can't name the purpose, remove it.
|
||||||
|
- **Don't animate high-frequency interactions.** If the user triggers it dozens of times a session, motion becomes friction. Instant is correct.
|
||||||
|
- **Don't animate keyboard-initiated actions.** Keyboard shortcuts should never animate — the user wants speed, not a show.
|
||||||
|
- **Don't add looping attention-seeking motion.** No pulsing dots, glowing status rings, breathing CTAs, throbbing indicators, or any looped scale/opacity pulse to draw the eye. They age badly, harm accessibility, and rarely serve the user. Use a static treatment unless the user explicitly asks for a pulse.
|
||||||
|
|
||||||
|
## Wrong defaults
|
||||||
|
|
||||||
|
- **Don't start from `scale(0)`.** It produces unnatural motion. Start from `scale(0.9)` or higher.
|
||||||
|
- **Don't use bare `ease` or `ease-in-out`.** Built-in curves lack strength. Use a custom `cubic-bezier` or a spring.
|
||||||
|
- **Don't give enter and exit equal weight.** Exits should be subtler — smaller translate, the user's attention is already moving on.
|
||||||
|
- **Don't use one duration for everything.** Smaller elements animate faster. Match duration to element size and context.
|
||||||
|
- **Don't ignore `transform-origin`.** Dropdowns, popovers, and tooltips should expand from their trigger, not from center.
|
||||||
|
|
||||||
|
## Performance failures
|
||||||
|
|
||||||
|
- **Don't animate layout properties.** `width`, `height`, `top`, `left`, `margin`, `padding` trigger reflow. Use `transform` and `opacity`.
|
||||||
|
- **Don't sprinkle `will-change` everywhere.** It's a targeted hint for elements about to animate, not a global fix.
|
||||||
|
- **Don't use keyframes for interruptible animations.** Keyframes can't retarget mid-flight. Use state-driven CSS transitions for anything the user can re-trigger rapidly.
|
||||||
|
|
||||||
|
## Accessibility omissions
|
||||||
|
|
||||||
|
- **Don't ship motion without `prefers-reduced-motion`.** Every animation you generate needs a reduced-motion path. This is not optional and not a follow-up — include it in the same code.
|
||||||
|
- **Don't use vestibular triggers casually.** Large-scale zoom, spin, and parallax can cause genuine discomfort. Avoid unless the design explicitly calls for it, and gate them behind reduced-motion.
|
||||||
|
|
||||||
|
## Context blindness
|
||||||
|
|
||||||
|
- **Don't apply one designer's rules universally.** Emil's sub-300ms restraint is wrong for a kids' app; Jhey's elastic playfulness is wrong for a banking dashboard. Confirm the weighting before generating.
|
||||||
|
- **Don't ignore the existing codebase.** If the project already animates with 500ms springs, a new 150ms ease-out component will feel foreign. Match established conventions unless they are the thing being fixed.
|
||||||
@ -0,0 +1,208 @@
|
|||||||
|
<!--
|
||||||
|
Demo Shell — design-motion-principles v2.1
|
||||||
|
===========================================
|
||||||
|
|
||||||
|
This file is a template the audit agent reads during STEP 3 of the
|
||||||
|
audit workflow (see ../SKILL.md). The agent embeds one .demo-card per
|
||||||
|
Critical or Important finding (per R4 in the plan — Opportunities do
|
||||||
|
not get demo cards).
|
||||||
|
|
||||||
|
How to use this file as the agent
|
||||||
|
---------------------------------
|
||||||
|
1. Copy the entire <style> block into the report's <head>. The shell's
|
||||||
|
CSS variables, .demo-card layout, loop indicator, and the
|
||||||
|
prefers-reduced-motion guard are shared across all demo cards in
|
||||||
|
the report — they are not duplicated per finding.
|
||||||
|
|
||||||
|
2. For each finding {n} (1-indexed across the whole report):
|
||||||
|
a. Replace the MOTION-CODE-SLOT-{n} comment in <style> with the
|
||||||
|
per-finding @keyframes block AND any .demo-card-{n}__motion-target
|
||||||
|
selector rules. Use the suffix {n} so multiple findings in one
|
||||||
|
report do not collide on keyframe names or target selectors.
|
||||||
|
b. Replace the DEMO-CARD-MOTION-SLOT-{n} comment in the .demo-card
|
||||||
|
markup with the actual motion-target element. Its class must be
|
||||||
|
.demo-card-{n}__motion-target so it matches the rules above.
|
||||||
|
c. Set the .demo-card__header text to a short title for the
|
||||||
|
recommended motion (e.g., "Subtle enter: opacity + translateY + blur").
|
||||||
|
d. Set the .demo-card__subhead text to the duration plus easing or
|
||||||
|
other relevant values (e.g., "300ms · ease-out"). The subhead
|
||||||
|
ALWAYS renders — the agent populates it for every demo so card
|
||||||
|
heights stay consistent across the report.
|
||||||
|
|
||||||
|
3. Per-finding code MUST honor these contracts:
|
||||||
|
- Do not redefine the shell's CSS variables (--bg, --fg, --border,
|
||||||
|
--accent, --loop-dim, --card-radius, --card-padding, --gap,
|
||||||
|
--sans, --mono). Use them via var().
|
||||||
|
- Do not modify the prefers-reduced-motion block. The shell's
|
||||||
|
guard collapses all .demo-card-{n}__motion-target animations to
|
||||||
|
none. The per-finding @keyframes 100% state must match the
|
||||||
|
motion-target's default rendered state so the reduce-motion
|
||||||
|
fallback shows the correct final visual.
|
||||||
|
- Per-finding @keyframes use the 0% / 66% / 100% cadence:
|
||||||
|
0% = start state, 66% = motion complete (~2s in), 100% = hold
|
||||||
|
(~1s). The shell uses animation-duration: 3s.
|
||||||
|
|
||||||
|
4. Demo cards are non-interactive. They have no hover or focus state
|
||||||
|
beyond the default outline suppression. tabindex="-1" keeps them
|
||||||
|
out of keyboard nav order — readers tab through findings, not
|
||||||
|
through demo cards.
|
||||||
|
|
||||||
|
Loop pacing
|
||||||
|
-----------
|
||||||
|
animation-duration: 3s. Keyframes 0% / 66% / 100%. Motion 0-66% = ~2s,
|
||||||
|
hold 66-100% = ~1s, then the animation restarts. Per-finding code
|
||||||
|
imitates this cadence so all demos in a report share the same rhythm.
|
||||||
|
|
||||||
|
Empty state (this file rendered standalone)
|
||||||
|
-------------------------------------------
|
||||||
|
Opening this file directly in a browser shows one .demo-card with the
|
||||||
|
loop indicator and placeholder content. No motion plays — the agent
|
||||||
|
injects motion per finding when this template is embedded in a report.
|
||||||
|
-->
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Demo Shell — design-motion-principles</title>
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
--bg: #ffffff;
|
||||||
|
--fg: #111111;
|
||||||
|
--border: #e5e5e5;
|
||||||
|
--accent: #111111;
|
||||||
|
--loop-dim: rgba(0, 0, 0, 0.45);
|
||||||
|
--card-radius: 12px;
|
||||||
|
--card-padding: 20px;
|
||||||
|
--gap: 12px;
|
||||||
|
--sans: system-ui, -apple-system, "Segoe UI", sans-serif;
|
||||||
|
--mono: ui-monospace, "SF Mono", Menlo, monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
:root {
|
||||||
|
--bg: #1a1a1a;
|
||||||
|
--fg: #f0f0f0;
|
||||||
|
--border: #333333;
|
||||||
|
--accent: #f0f0f0;
|
||||||
|
--loop-dim: rgba(240, 240, 240, 0.45);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background: var(--bg);
|
||||||
|
color: var(--fg);
|
||||||
|
font-family: var(--sans);
|
||||||
|
margin: 0;
|
||||||
|
padding: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-card {
|
||||||
|
position: relative;
|
||||||
|
background: var(--bg);
|
||||||
|
color: var(--fg);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--card-radius);
|
||||||
|
padding: var(--card-padding);
|
||||||
|
max-width: 360px;
|
||||||
|
min-width: 280px;
|
||||||
|
font-family: var(--sans);
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-card:focus-visible {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-card__header {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--fg);
|
||||||
|
margin-bottom: 4px;
|
||||||
|
padding-right: 72px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-card__subhead {
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: var(--loop-dim);
|
||||||
|
margin-bottom: var(--gap);
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-card__stage {
|
||||||
|
background: var(--bg);
|
||||||
|
border: 1px dashed var(--border);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 24px;
|
||||||
|
min-height: 100px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-card__loop-indicator {
|
||||||
|
position: absolute;
|
||||||
|
top: 12px;
|
||||||
|
right: 12px;
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 0.7rem;
|
||||||
|
color: var(--loop-dim);
|
||||||
|
letter-spacing: 0.02em;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.demo-card__loop-indicator {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
[class*="__motion-target"] {
|
||||||
|
animation: none !important;
|
||||||
|
transition: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* MOTION-CODE-SLOT-{n}
|
||||||
|
Per-finding @keyframes and .demo-card-{n}__motion-target rules
|
||||||
|
go here. {n} = finding's 1-indexed position across the report.
|
||||||
|
|
||||||
|
Example shape (replace per finding):
|
||||||
|
|
||||||
|
@keyframes motion-1-enter {
|
||||||
|
0% { opacity: 0; transform: translateY(8px); filter: blur(4px); }
|
||||||
|
66% { opacity: 1; transform: translateY(0); filter: blur(0); }
|
||||||
|
100% { opacity: 1; transform: translateY(0); filter: blur(0); }
|
||||||
|
}
|
||||||
|
.demo-card-1__motion-target {
|
||||||
|
animation: motion-1-enter 3s infinite;
|
||||||
|
display: inline-block;
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-radius: 6px;
|
||||||
|
background: var(--accent);
|
||||||
|
color: var(--bg);
|
||||||
|
font-family: var(--sans);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<article class="demo-card" tabindex="-1">
|
||||||
|
<div class="demo-card__loop-indicator">↻ looping</div>
|
||||||
|
<div class="demo-card__header">Recommended motion title</div>
|
||||||
|
<div class="demo-card__subhead">300ms · ease-out</div>
|
||||||
|
<div class="demo-card__stage">
|
||||||
|
<!-- DEMO-CARD-MOTION-SLOT-{n}
|
||||||
|
Per-finding motion-target element goes here. Its class must
|
||||||
|
match the rules in MOTION-CODE-SLOT-{n} above:
|
||||||
|
<div class="demo-card-{n}__motion-target">...</div>
|
||||||
|
The element's contents are agent-determined (a button shape,
|
||||||
|
a card, an icon, a list of items for stagger demos, etc.). -->
|
||||||
|
<span style="color: var(--loop-dim); font-family: var(--mono); font-size: 0.75rem;">
|
||||||
|
(motion preview renders here per finding)
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@ -0,0 +1,121 @@
|
|||||||
|
# Emil Kowalski's Animation Principles
|
||||||
|
|
||||||
|
Emil Kowalski is a Design Engineer at Linear (previously Vercel). Creator of Sonner, Vaul, and the "Animations on the Web" course. His approach emphasizes **restraint, speed, and purposeful motion**.
|
||||||
|
|
||||||
|
This file is Emil's **philosophy and decision frameworks** — the judgment for *whether* and *when* to animate. Implementation code lives in `motion-cookbook.md` (cross-referenced below).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Core Philosophy: Restraint & Purpose
|
||||||
|
|
||||||
|
Emil's defining contribution to motion design thinking is knowing **when NOT to animate**.
|
||||||
|
|
||||||
|
> "The goal is not to animate for animation's sake, it's to build great user interfaces."
|
||||||
|
|
||||||
|
His key question for any interaction: **"Should this animate at all?"**
|
||||||
|
|
||||||
|
### The Frequency Rule
|
||||||
|
|
||||||
|
Animation appropriateness depends on interaction frequency:
|
||||||
|
|
||||||
|
| Frequency | Recommendation |
|
||||||
|
|-----------|----------------|
|
||||||
|
| Rare (monthly) | Delightful, morphing animations welcome |
|
||||||
|
| Occasional (daily) | Subtle, fast animations |
|
||||||
|
| Frequent (100s/day) | No animation or instant transitions |
|
||||||
|
| Keyboard-initiated | Never animate |
|
||||||
|
|
||||||
|
**The Raycast example**: A tool used constantly throughout the day benefits from zero animation. Users with clear goals "don't expect to be delighted" and prioritize frictionless workflow.
|
||||||
|
|
||||||
|
### Speed is Non-Negotiable
|
||||||
|
|
||||||
|
> "UI animations should generally stay under 300ms."
|
||||||
|
|
||||||
|
A 180ms animation feels more responsive than 400ms. Speed creates perceived performance. When in doubt, go faster.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## The 7 Practical Animation Tips
|
||||||
|
|
||||||
|
Emil's decision checklist. Code for each is in the cookbook section noted.
|
||||||
|
|
||||||
|
1. **Scale your buttons** — Subtle `scale(0.97)` on `:active` for immediate tactile feedback. → cookbook §10
|
||||||
|
2. **Don't animate from `scale(0)`** — It creates unnatural motion. Start from `scale(0.9)` or higher. → cookbook §10
|
||||||
|
3. **Tooltip delay patterns** — First tooltip in a group: delay + animation. Subsequent: instant. → cookbook §10
|
||||||
|
4. **Custom easing is essential** — *"Easing is the most important part of any animation. It can make a bad animation feel great."* Built-in `ease`/`ease-in-out` lack strength; use custom Bézier curves (easing.dev, easings.co). → cookbook §2
|
||||||
|
5. **Origin-aware animations** — Motion should originate from its logical source; a dropdown expands from its trigger, not from center. → cookbook §13
|
||||||
|
6. **Keep animations fast** — Under 300ms for UI; remove animation entirely for high-frequency interactions.
|
||||||
|
7. **Use blur when nothing else works** — `filter: blur(2px)` masks imperfections during rough state transitions. → cookbook §10
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Signature Techniques (decision context — code in cookbook)
|
||||||
|
|
||||||
|
| Technique | When Emil reaches for it | Cookbook |
|
||||||
|
|-----------|--------------------------|----------|
|
||||||
|
| Clip-path animations | Reveals and tab transitions — hardware-accelerated, no layout shift, no extra DOM | §9 |
|
||||||
|
| Spring physics | Any value that should interpolate smoothly rather than snap (e.g. mouse position) | §12 |
|
||||||
|
| CSS transitions over keyframes | Anything the user can rapidly re-trigger — keyframes can't retarget mid-flight | §11 |
|
||||||
|
| Direct style updates | Frequent updates like drag — CSS variables cause cascade recalculation | §11 |
|
||||||
|
| Momentum-based dismissal | Swipe-to-dismiss — use velocity, not distance thresholds | §11 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Interruptibility
|
||||||
|
|
||||||
|
Great animations can be interrupted mid-play and respond naturally. Framer Motion supports this natively; CSS transitions allow smooth interruption before completion. **Test by clicking rapidly** — animations should blend, not queue.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## When to Use Each Approach
|
||||||
|
|
||||||
|
| Context | Approach |
|
||||||
|
|---------|----------|
|
||||||
|
| Keyboard shortcuts | No animation |
|
||||||
|
| High-frequency tool | Minimal or no animation |
|
||||||
|
| Daily-use feature | Fast, subtle animation (180-250ms) |
|
||||||
|
| Onboarding/first-time | Delightful animations welcome |
|
||||||
|
| Marketing/landing page | Full creative expression |
|
||||||
|
| Banking/serious UI | Minimal, functional motion |
|
||||||
|
| Playful brand | Bouncy, elastic easing appropriate |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Invisible Quality: Lessons from Sonner & Vaul
|
||||||
|
|
||||||
|
Emil's open-source libraries (Sonner for toasts, Vaul for drawers) reveal his philosophy in shipped code. The throughline: every detail serves **invisible quality** — users shouldn't notice polished interactions, they should just feel right.
|
||||||
|
|
||||||
|
> "When a feature functions as you assume it should, you proceed without giving it a second thought, which is our goal."
|
||||||
|
|
||||||
|
Details that matter: matching native motion curves for familiarity (Vaul uses iOS's `cubic-bezier(0.32, 0.72, 0, 1)`), damping near boundaries (*"things in real life don't suddenly stop, they slow down first"*), multi-touch protection, pointer capture during drags, pausing timers when the tab is inactive.
|
||||||
|
|
||||||
|
### Sonner Defaults
|
||||||
|
|
||||||
|
| Setting | Value | Rationale |
|
||||||
|
|---------|-------|-----------|
|
||||||
|
| Duration | 4000ms | Long enough to read, short enough to not annoy |
|
||||||
|
| Animation | 400ms ease | Smooth but snappy |
|
||||||
|
| Position | bottom-right | Convention, out of primary content |
|
||||||
|
| Dismissible | true | User control by default |
|
||||||
|
|
||||||
|
### Vaul Defaults
|
||||||
|
|
||||||
|
| Setting | Value | Rationale |
|
||||||
|
|---------|-------|-----------|
|
||||||
|
| Duration | 500ms | Match iOS sheet feel |
|
||||||
|
| Easing | cubic-bezier(0.32, 0.72, 0, 1) | iOS-native curve |
|
||||||
|
| Modal | true | Focus management, overlay |
|
||||||
|
| Direction | bottom | Convention for mobile sheets |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Emil vs. Jakub vs. Jhey
|
||||||
|
|
||||||
|
| Aspect | Emil | Jakub | Jhey |
|
||||||
|
|--------|------|-------|------|
|
||||||
|
| **Focus** | Restraint & speed | Subtle polish | Playful experimentation |
|
||||||
|
| **Key question** | "Should this animate?" | "Is this subtle enough?" | "What could this become?" |
|
||||||
|
| **Signature technique** | Frequency-based decisions | Blur + opacity + translateY | CSS custom properties |
|
||||||
|
| **Ideal context** | High-frequency tools | Production polish | Learning & exploration |
|
||||||
|
|
||||||
|
**Synthesis**: Use Emil's framework to decide IF you should animate. Use Jakub's techniques for HOW to animate in production. Use Jhey's approach for learning and experimentation.
|
||||||
@ -0,0 +1,80 @@
|
|||||||
|
# Jakub Krehel's Animation Principles
|
||||||
|
|
||||||
|
Jakub Krehel is a design engineer known for his work at jakub.kr. His approach emphasizes **subtle production polish** — animations that enhance the experience invisibly, designed for real client work where users interact repeatedly.
|
||||||
|
|
||||||
|
This file is Jakub's **philosophy and decision frameworks** — the judgment for *how polished* and *how subtle* motion should be. Implementation code lives in `motion-cookbook.md` (cross-referenced below).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Core Philosophy: Invisible Enhancement
|
||||||
|
|
||||||
|
> "The best animation is that which goes unnoticed."
|
||||||
|
|
||||||
|
His key question for any interaction: **"Is this subtle and polished enough for production?"**
|
||||||
|
|
||||||
|
Jakub's work embodies **refinement for production use**. His animations are:
|
||||||
|
|
||||||
|
- **Barely noticeable** — If users consciously notice the animation, it's probably too much
|
||||||
|
- **Production-ready** — Designed for real client work, not demos
|
||||||
|
- **Contextually appropriate** — Adapts to light mode, varied backgrounds, real content
|
||||||
|
- **Subtle over flashy** — The goal is to make interfaces feel smooth and responsive, not impressive
|
||||||
|
|
||||||
|
**The best compliment**: "This feels really nice" — not "cool animation!"
|
||||||
|
|
||||||
|
**The test**: If you remove the animation, do users feel something is missing? Good. If users comment "nice animation!" every time they see it? Too prominent.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## When to Apply Jakub's Mindset
|
||||||
|
|
||||||
|
- Production applications and client work
|
||||||
|
- Professional/enterprise interfaces
|
||||||
|
- When users will interact repeatedly (animations must not get tiresome)
|
||||||
|
- When accessibility and performance are critical
|
||||||
|
- When you need polish without distraction
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Signature Techniques (decision context — code in cookbook)
|
||||||
|
|
||||||
|
| Technique | When Jakub reaches for it | Cookbook |
|
||||||
|
|-----------|---------------------------|----------|
|
||||||
|
| Enter recipe (opacity + translateY + blur) | Any element appearing — blur creates a "materializing" feel more physical than fade alone | §1 |
|
||||||
|
| Subtler exits | Always — exits don't need the same movement or attention as enters; the user is moving on | §1 |
|
||||||
|
| Spring animations (`bounce: 0`) | Production motion — smooth deceleration without overshoot; reserve `bounce > 0` for playful contexts | §2 |
|
||||||
|
| Shadows instead of borders | Light mode on varied backgrounds — shadows adapt via transparency where solid borders clash | §3 |
|
||||||
|
| oklch gradients | Any gradient — interpolates through perceptually uniform space, avoiding muddy midpoints | §3 |
|
||||||
|
| Blur as a signal | Materializing in/out — blur→sharp = entering focus, sharp→blur = losing relevance | §3 |
|
||||||
|
| Optical alignment | Buttons with icons, play buttons, asymmetric shapes — trust your eyes over math | §4 |
|
||||||
|
| Animated icon swaps (opacity + scale + blur) | Contextual icon changes (copy→check) — instant swaps feel jarring and get missed | §5 |
|
||||||
|
| Shared layout via `layoutId` | Smooth FLIP transitions between different components (card→modal) | §6 |
|
||||||
|
| Targeted `will-change` | Specific properties about to animate — never global | see `performance.md` |
|
||||||
|
|
||||||
|
**Decision rules baked into the techniques:**
|
||||||
|
- Exits should always be subtler than enters — smaller movement, same blur.
|
||||||
|
- `bounce: 0` is the production default; bounce above zero reads as playful.
|
||||||
|
- Borders are fine in dark mode or when you want intentional hard edges.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Common Mistakes (Jakub's Perspective)
|
||||||
|
|
||||||
|
- **Making enter and exit animations equally prominent** — Exits should be subtler
|
||||||
|
- **Using solid borders when shadows would adapt better** — Especially on varied backgrounds
|
||||||
|
- **Forgetting optical alignment** — Buttons with icons, play buttons, asymmetric shapes
|
||||||
|
- **Over-animating** — If users notice the animation itself, it's too much
|
||||||
|
- **Using the same animation everywhere** — Context should drive timing and easing choices
|
||||||
|
- **Ignoring hover state transitions** — Even small transitions (150-200ms) feel more polished than instant changes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Jakub vs. Emil vs. Jhey
|
||||||
|
|
||||||
|
| Aspect | Jakub | Emil | Jhey |
|
||||||
|
|--------|-------|------|------|
|
||||||
|
| **Focus** | Subtle polish | Restraint & speed | Playful experimentation |
|
||||||
|
| **Key question** | "Is this subtle enough?" | "Should this animate?" | "What could this become?" |
|
||||||
|
| **Signature technique** | Blur + opacity + translateY | Frequency-based decisions | CSS custom properties |
|
||||||
|
| **Ideal context** | Production polish | High-frequency tools | Learning & exploration |
|
||||||
|
|
||||||
|
**When to use Jakub**: You've decided something should animate (passed Emil's gate) and need to make it production-ready and polished.
|
||||||
@ -0,0 +1,122 @@
|
|||||||
|
# Jhey Tompkins' Animation Principles
|
||||||
|
|
||||||
|
Jhey Tompkins (@jh3yy) is a design engineer known for pushing the boundaries of CSS and creative coding. His approach emphasizes **playful experimentation** — learning through building whimsical projects where the joy of creation drives skill development.
|
||||||
|
|
||||||
|
This file is Jhey's **philosophy and decision frameworks** — the judgment for *creative expression* and *easing personality*. Implementation code lives in `motion-cookbook.md` (cross-referenced below).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Core Philosophy: Learn Through Play
|
||||||
|
|
||||||
|
> "I went from 'I want to learn X, so how do I fit it into Y' to 'I want to make Y, can I learn X to do it?'"
|
||||||
|
|
||||||
|
His key question for any interaction: **"What could this become?"**
|
||||||
|
|
||||||
|
**The motivation should be making something cool — learning is a happy side effect.**
|
||||||
|
|
||||||
|
### Core Beliefs
|
||||||
|
|
||||||
|
- **No idea is a bad idea** — Document every spark, however weird
|
||||||
|
- **Don't ask "Why?" or "Is this practical?"** — Make what brings you joy first
|
||||||
|
- **"Useless" demos teach real skills** — CSS art teaches clip-path mastery, border-radius tricks, stacking contexts
|
||||||
|
- **Lateral learning** — Building diverse demos trains you to switch contexts and rise to challenges
|
||||||
|
- **You'll never have time to make everything** — And that's okay. The act of documenting ideas matters.
|
||||||
|
|
||||||
|
**Keep notebooks everywhere** — including by your bed. Creative sparks happen at random times.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## When to Apply Jhey's Mindset
|
||||||
|
|
||||||
|
- Creative sites, portfolios, kids apps — contexts where delight is the point
|
||||||
|
- Learning new techniques
|
||||||
|
- Personal projects and experiments
|
||||||
|
- When you're stuck in a creative rut
|
||||||
|
- Exploring what's possible with new CSS features
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## The Golden Rule (Even in Play)
|
||||||
|
|
||||||
|
> "The best animation is that which goes unnoticed."
|
||||||
|
|
||||||
|
Even in playful contexts, effective motion enhances the experience without demanding attention, feels natural and expected, serves a functional purpose, and doesn't fatigue users on repeated interactions.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Easing Has Personality (Decision Framework)
|
||||||
|
|
||||||
|
> "Duration is all about timing, and timing has a big impact on the movement's naturalness."
|
||||||
|
|
||||||
|
Each easing curve communicates something. **Context matters more than rules.**
|
||||||
|
|
||||||
|
> "You wouldn't use 'Elastic' for a bank's website, but it might work perfectly for an energetic site for children."
|
||||||
|
|
||||||
|
Brand personality should drive easing choices — a playful brand can use bouncy/elastic easing, a professional brand should use subtle springs or `ease-out`.
|
||||||
|
|
||||||
|
**When NOT to use bouncy/elastic easing:**
|
||||||
|
- Professional/enterprise applications
|
||||||
|
- Frequently repeated interactions (gets tiresome)
|
||||||
|
- Error states or serious UI
|
||||||
|
- When users need to complete tasks quickly
|
||||||
|
|
||||||
|
(Easing-feel reference table and the `linear()` recipe for pure-CSS bounce/elastic/spring → cookbook §2.)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Signature Techniques (decision context — code in cookbook)
|
||||||
|
|
||||||
|
| Technique | When Jhey reaches for it | Cookbook |
|
||||||
|
|-----------|--------------------------|----------|
|
||||||
|
| `linear()` function | Pure-CSS bounce, elastic, spring effects without JS | §2 |
|
||||||
|
| `@property` | Animating CSS custom properties — type declaration unlocks interpolation | §7 |
|
||||||
|
| Decomposed transforms | Curved motion paths impossible with a monolithic transform | §7 |
|
||||||
|
| `animation-fill-mode` | Delayed fade-in sequences — `backwards` prevents the pre-animation flash | §1 |
|
||||||
|
| Negative delays | "Already in progress" stagger effects | §2 |
|
||||||
|
| Scoped CSS variables | Varied behavior from a single animation definition | §7 |
|
||||||
|
| 3D CSS ("think in cubes") | Decompose 3D objects into cuboids; `preserve-3d` + `perspective` | §8 |
|
||||||
|
| Scroll-driven with duration | Decouple animation timing from scroll speed | §14 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Why "Useless" CSS Art Matters
|
||||||
|
|
||||||
|
CSS art teaches real skills that transfer to production: clip-path mastery, border-radius tricks, stacking contexts, complex gradients, pseudo-element layering. For complex illustrations — break into simple shapes, use pseudo-elements liberally, layer with z-index carefully, use CSS variables for repeated values, don't fear many elements.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## When to Experiment vs. Ship
|
||||||
|
|
||||||
|
| Situation | Approach |
|
||||||
|
|-----------|----------|
|
||||||
|
| Learning a new CSS feature | Build something weird and fun |
|
||||||
|
| Portfolio piece | Push boundaries, show creativity |
|
||||||
|
| Personal project | Follow your joy |
|
||||||
|
| Client work | Apply Jakub's production polish instead |
|
||||||
|
| High-frequency tool | Apply Emil's restraint instead |
|
||||||
|
|
||||||
|
The playful approach is for **learning and exploration**. For production, switch to Jakub or Emil's mindset.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Common Mistakes (Jhey's Perspective)
|
||||||
|
|
||||||
|
- **Filtering ideas based on "usefulness" too early** — Make first, judge later
|
||||||
|
- **Not documenting random creative sparks** — Keep notebooks everywhere
|
||||||
|
- **Thinking CSS art is useless** — It teaches real skills
|
||||||
|
- **Focusing on "How do I learn X?" instead of "How do I make Y?"** — Let ideas drive learning
|
||||||
|
- **Following tutorials without experimenting** — Tutorials teach techniques; experimentation teaches problem-solving
|
||||||
|
- **Giving up when something doesn't work** — The struggle is where learning happens
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Jhey vs. Emil vs. Jakub
|
||||||
|
|
||||||
|
| Aspect | Jhey | Emil | Jakub |
|
||||||
|
|--------|------|------|-------|
|
||||||
|
| **Focus** | Playful experimentation | Restraint & speed | Subtle polish |
|
||||||
|
| **Key question** | "What could this become?" | "Should this animate?" | "Is this subtle enough?" |
|
||||||
|
| **Signature technique** | CSS custom properties | Frequency-based decisions | Blur + opacity + translateY |
|
||||||
|
| **Ideal context** | Learning & exploration | High-frequency tools | Production polish |
|
||||||
|
|
||||||
|
**When to use Jhey**: You're building something where delight is the goal, exploring what's possible, or learning a new technique. The skills transfer to production work later.
|
||||||
@ -0,0 +1,529 @@
|
|||||||
|
# Motion Cookbook
|
||||||
|
|
||||||
|
The single source of truth for motion **recipes** — implementation patterns and code. In Create mode this is your primary reference; in Audit mode load it when making implementation recommendations. Designer philosophy and decision frameworks live in the per-designer reference files; the code lives here.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Enter & Exit Animations
|
||||||
|
|
||||||
|
### Enter Animation Recipe (Jakub)
|
||||||
|
A standard enter animation combines three properties:
|
||||||
|
- **Opacity**: 0 → 1
|
||||||
|
- **TranslateY**: ~8px → 0 (or calc(-100% - 4px) for full container slides)
|
||||||
|
- **Blur**: 4px → 0px
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
initial={{ opacity: 0, translateY: "calc(-100% - 4px)", filter: "blur(4px)" }}
|
||||||
|
animate={{ opacity: 1, translateY: 0, filter: "blur(0px)" }}
|
||||||
|
transition={{ type: "spring", duration: 0.45, bounce: 0 }}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why blur?** It creates a "materializing" effect that feels more physical than opacity alone. The element appears to come into focus, not just fade in.
|
||||||
|
|
||||||
|
### Exit Animation Subtlety (Jakub)
|
||||||
|
**Key Insight**: Exit animations should be subtler than enter animations.
|
||||||
|
|
||||||
|
When a component exits, it doesn't need the same amount of movement or attention as when entering. The user's focus is moving to what comes next, not what's leaving.
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
// Instead of full exit movement:
|
||||||
|
exit={{ translateY: "calc(-100% - 4px)" }}
|
||||||
|
|
||||||
|
// Use a subtle fixed value:
|
||||||
|
exit={{ translateY: "-12px", opacity: 0, filter: "blur(4px)" }}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why this works**: Exits become softer, less jarring, and don't compete for attention with whatever is entering or remaining.
|
||||||
|
|
||||||
|
**When NOT to use subtle exits**:
|
||||||
|
- When the exit itself is meaningful (user-initiated dismissal)
|
||||||
|
- When you need to emphasize something leaving (error clearing, item deletion)
|
||||||
|
- Full-page transitions where directional continuity matters
|
||||||
|
|
||||||
|
### Fill Mode for Persistence (Jhey)
|
||||||
|
Use `animation-fill-mode` to prevent jarring visual resets:
|
||||||
|
- `forwards`: Element retains animation styling after completion
|
||||||
|
- `backwards`: Element retains style from first keyframe before animation starts
|
||||||
|
- `both`: Retains styling in both directions
|
||||||
|
|
||||||
|
**Critical for**: Fade-in sequences with delays. Without `backwards`, elements flash at full opacity before their delayed animation starts, then pop to invisible, then fade in.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Easing & Timing
|
||||||
|
|
||||||
|
### Duration Impacts Naturalness
|
||||||
|
> "Duration is all about timing, and timing has a big impact on the movement's naturalness." — Jhey Tompkins
|
||||||
|
|
||||||
|
### Custom Easing is Essential (Emil)
|
||||||
|
> "Easing is the most important part of any animation. It can make a bad animation feel great."
|
||||||
|
|
||||||
|
Built-in CSS easing (`ease`, `ease-in-out`) lacks strength. Always use custom Bézier curves for professional results. Resources: easing.dev, easings.co
|
||||||
|
|
||||||
|
### Easing Selection Guidelines (Jhey)
|
||||||
|
Each easing curve communicates something to the viewer. **Context matters more than rules.**
|
||||||
|
|
||||||
|
| Easing | Feel | Good For |
|
||||||
|
|--------|------|----------|
|
||||||
|
| `ease-out` | Fast start, gentle stop | Elements entering view (arriving) |
|
||||||
|
| `ease-in` | Gentle start, fast exit | Elements leaving view (departing) |
|
||||||
|
| `ease-in-out` | Gentle both ends | Elements changing state while visible |
|
||||||
|
| `linear` | Constant speed | Continuous loops, progress indicators |
|
||||||
|
| `spring` | Natural deceleration | Interactive elements, professional UI |
|
||||||
|
|
||||||
|
**The Context Rule**:
|
||||||
|
> "You wouldn't use 'Elastic' for a bank's website, but it might work perfectly for an energetic site for children."
|
||||||
|
|
||||||
|
Brand personality should drive easing choices. A playful brand can use bouncy, elastic easing. A professional brand should use subtle springs or ease-out.
|
||||||
|
|
||||||
|
**When NOT to use bouncy/elastic easing**:
|
||||||
|
- Professional/enterprise applications
|
||||||
|
- Frequently repeated interactions (gets tiresome)
|
||||||
|
- Error states or serious UI
|
||||||
|
- When users need to complete tasks quickly
|
||||||
|
|
||||||
|
### Spring Animations (Jakub)
|
||||||
|
Prefer spring animations over linear/ease for more natural-feeling motion:
|
||||||
|
```jsx
|
||||||
|
transition={{ type: "spring", duration: 0.45, bounce: 0 }}
|
||||||
|
transition={{ type: "spring", duration: 0.55, bounce: 0.1 }}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why `bounce: 0`?** It gives smooth deceleration without overshoot—professional and refined. Reserve bounce > 0 for playful contexts.
|
||||||
|
|
||||||
|
### The linear() Function (Jhey)
|
||||||
|
CSS `linear()` enables bounce, elastic, and spring effects in pure CSS:
|
||||||
|
```css
|
||||||
|
:root {
|
||||||
|
--bounce-easing: linear(
|
||||||
|
0, 0.004, 0.016, 0.035, 0.063, 0.098, 0.141 13.6%, 0.25, 0.391, 0.563, 0.765,
|
||||||
|
1, 0.891 40.9%, 0.848, 0.813, 0.785, 0.766, 0.754, 0.75, 0.754, 0.766, 0.785,
|
||||||
|
0.813, 0.848, 0.891 68.2%, 1 72.7%, 0.973, 0.953, 0.941, 0.938, 0.941, 0.953,
|
||||||
|
0.973, 1, 0.988, 0.984, 0.988, 1
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Use Jake Archibald's linear() generator for custom curves: https://linear-easing-generator.netlify.app/
|
||||||
|
|
||||||
|
### Stagger Techniques (Jhey)
|
||||||
|
`animation-delay` only applies once (not per iteration). Approaches:
|
||||||
|
|
||||||
|
1. **Different delays with finite iterations** — Works for one-time sequences
|
||||||
|
2. **Pad keyframes** to create stagger within the animation:
|
||||||
|
```css
|
||||||
|
@keyframes spin {
|
||||||
|
0%, 50% { transform: rotate(0deg); }
|
||||||
|
100% { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Negative delays** for "already in progress" effects:
|
||||||
|
```css
|
||||||
|
.element { animation-delay: calc(var(--index) * -0.2s); }
|
||||||
|
```
|
||||||
|
This makes animations appear mid-flight from the start—useful for staggered continuous animations.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Visual Effects
|
||||||
|
|
||||||
|
### Shadows Instead of Borders (Jakub)
|
||||||
|
In light mode, prefer subtle multi-layer box-shadows over solid borders:
|
||||||
|
```css
|
||||||
|
.card {
|
||||||
|
box-shadow:
|
||||||
|
0px 0px 0px 1px rgba(0, 0, 0, 0.06),
|
||||||
|
0px 1px 2px -1px rgba(0, 0, 0, 0.06),
|
||||||
|
0px 2px 4px 0px rgba(0, 0, 0, 0.04);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Slightly darker on hover */
|
||||||
|
.card:hover {
|
||||||
|
box-shadow:
|
||||||
|
0px 0px 0px 1px rgba(0, 0, 0, 0.08),
|
||||||
|
0px 1px 2px -1px rgba(0, 0, 0, 0.08),
|
||||||
|
0px 2px 4px 0px rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why shadows over borders?**
|
||||||
|
- Shadows adapt to any background (images, gradients, varied colors) because they use transparency
|
||||||
|
- Borders are solid colors that may clash with dynamic backgrounds
|
||||||
|
- Multi-layer shadows create depth; single borders feel flat
|
||||||
|
- Shadows can be transitioned smoothly with `transition: box-shadow`
|
||||||
|
|
||||||
|
**When borders are fine**:
|
||||||
|
- Dark mode (shadows less visible anyway)
|
||||||
|
- When you need hard edges intentionally
|
||||||
|
- Simple interfaces where depth isn't needed
|
||||||
|
|
||||||
|
### Gradients & Color Spaces (Jakub)
|
||||||
|
- Use `oklch` for gradients to avoid muddy midpoints:
|
||||||
|
```css
|
||||||
|
element { background: linear-gradient(in oklch, blue, red); }
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Color hints** control where the blend midpoint appears (different from color stops)
|
||||||
|
- Layer gradients with `background-blend-mode` for unique effects
|
||||||
|
|
||||||
|
**Why oklch?** It interpolates through perceptually uniform color space, avoiding the gray/muddy zone that sRGB hits when blending complementary colors.
|
||||||
|
|
||||||
|
### Blur as a Signal (Jakub)
|
||||||
|
Blur (via `filter: blur()`) combined with opacity and translate creates a "materializing" effect. Use blur to signal:
|
||||||
|
- **Entering focus**: blur → sharp
|
||||||
|
- **Losing relevance**: sharp → blur
|
||||||
|
- **State transitions**: blur during, sharp after
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Optical Alignment
|
||||||
|
|
||||||
|
### Geometric vs. Optical (Jakub)
|
||||||
|
> "Sometimes it's necessary to break out of geometric alignment to make things feel visually balanced."
|
||||||
|
|
||||||
|
**Buttons with icons**: Reduce padding on the icon side so content appears centered:
|
||||||
|
```
|
||||||
|
[ Icon Text ] ← Geometric (mathematically centered, feels off)
|
||||||
|
[ Icon Text ] ← Optical (visually centered, feels right)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Play button icons**: The triangle points right, creating visual weight on the left. Shift it slightly right to appear centered.
|
||||||
|
|
||||||
|
**Icons in general**: Many icon packs account for optical balance, but asymmetric shapes (arrows, play, chevrons) may need manual margin/padding adjustment.
|
||||||
|
|
||||||
|
**The rule**: If it looks wrong despite being mathematically correct, trust your eyes and adjust.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Icon & State Animations (Jakub)
|
||||||
|
|
||||||
|
### Contextual Icon Transitions
|
||||||
|
When icons change contextually (copy → check, loading → done), animate:
|
||||||
|
- Opacity
|
||||||
|
- Scale
|
||||||
|
- Blur
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
<AnimatePresence mode="wait">
|
||||||
|
{isCopied ? (
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, scale: 0.8, filter: "blur(4px)" }}
|
||||||
|
animate={{ opacity: 1, scale: 1, filter: "blur(0px)" }}
|
||||||
|
exit={{ opacity: 0, scale: 0.8, filter: "blur(4px)" }}
|
||||||
|
>
|
||||||
|
<CheckIcon />
|
||||||
|
</motion.div>
|
||||||
|
) : (
|
||||||
|
<motion.div ...>
|
||||||
|
<CopyIcon />
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why animate icon swaps?** Instant swaps feel jarring and can be missed. Animated transitions:
|
||||||
|
- Draw attention to the state change
|
||||||
|
- Feel responsive and polished
|
||||||
|
- Give the user confidence their action registered
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Shared Layout Animations (Jakub)
|
||||||
|
|
||||||
|
### FLIP Technique via layoutId
|
||||||
|
Motion's `layoutId` prop enables smooth transitions between completely different components:
|
||||||
|
```jsx
|
||||||
|
// In one location:
|
||||||
|
<motion.div layoutId="card" className="small-card" />
|
||||||
|
|
||||||
|
// In another location:
|
||||||
|
<motion.div layoutId="card" className="large-card" />
|
||||||
|
```
|
||||||
|
|
||||||
|
Motion automatically animates between them using the FLIP technique (First, Last, Inverse, Play).
|
||||||
|
|
||||||
|
### Best Practices
|
||||||
|
- Keep elements with `layoutId` **outside** of `AnimatePresence` to avoid conflicts
|
||||||
|
- If inside `AnimatePresence`, the initial/exit animations will trigger during layout animation (looks bad with opacity)
|
||||||
|
- Multiple elements can animate if each has a unique `layoutId`
|
||||||
|
- Works for different heights, widths, positions, and even component types (card → modal)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. CSS Custom Properties & @property (Jhey)
|
||||||
|
|
||||||
|
### Type Specification Unlocks Animation
|
||||||
|
The `@property` rule lets you declare types for CSS variables, enabling smooth interpolation:
|
||||||
|
|
||||||
|
```css
|
||||||
|
@property --hue {
|
||||||
|
initial-value: 0;
|
||||||
|
inherits: false;
|
||||||
|
syntax: '<number>';
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes rainbow {
|
||||||
|
to { --hue: 360; }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Available types**: length, number, percentage, color, angle, time, integer, transform-list
|
||||||
|
|
||||||
|
**Why this matters**: Without `@property`, CSS sees custom properties as strings. Strings can't interpolate—they just swap. With a declared type, the browser knows how to smoothly transition between values.
|
||||||
|
|
||||||
|
### Decompose Complex Transforms
|
||||||
|
Instead of animating a monolithic transform (which can't interpolate curved paths), split into typed properties:
|
||||||
|
|
||||||
|
```css
|
||||||
|
@property --x { syntax: '<percentage>'; initial-value: 0%; inherits: false; }
|
||||||
|
@property --y { syntax: '<percentage>'; initial-value: 0%; inherits: false; }
|
||||||
|
|
||||||
|
.ball {
|
||||||
|
transform: translateX(var(--x)) translateY(var(--y));
|
||||||
|
animation: throw 1s;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes throw {
|
||||||
|
0% { --x: -500%; }
|
||||||
|
50% { --y: -250%; }
|
||||||
|
100% { --x: 500%; }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This creates curved motion paths that would be impossible with standard transform animation—the ball arcs through space rather than moving in straight lines.
|
||||||
|
|
||||||
|
### Scoped Variables for Dynamic Behavior (Jhey)
|
||||||
|
CSS custom properties respect scope, enabling powerful patterns:
|
||||||
|
```css
|
||||||
|
.item { --delay: 0; animation-delay: calc(var(--delay) * 100ms); }
|
||||||
|
.item:nth-child(1) { --delay: 0; }
|
||||||
|
.item:nth-child(2) { --delay: 1; }
|
||||||
|
.item:nth-child(3) { --delay: 2; }
|
||||||
|
```
|
||||||
|
|
||||||
|
Use scoped variables to create varied behavior from a single animation definition.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 3D CSS (Jhey)
|
||||||
|
|
||||||
|
### Think in Cuboids
|
||||||
|
> "Think in cubes instead of boxes" — Jhey Tompkins
|
||||||
|
|
||||||
|
Complex 3D scenes are assemblies of cube-shaped elements (like LEGO). Decompose any 3D object into cuboids.
|
||||||
|
|
||||||
|
### Essential Setup
|
||||||
|
```css
|
||||||
|
.scene {
|
||||||
|
transform-style: preserve-3d;
|
||||||
|
perspective: 1000px;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Responsive 3D
|
||||||
|
Use CSS variables for dimensions and `vmin` units:
|
||||||
|
```css
|
||||||
|
.cube {
|
||||||
|
--size: 10vmin;
|
||||||
|
width: var(--size);
|
||||||
|
height: var(--size);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Clip-Path Animations (Emil)
|
||||||
|
|
||||||
|
### Why clip-path?
|
||||||
|
- Hardware-accelerated rendering
|
||||||
|
- No layout shifts
|
||||||
|
- No additional DOM elements needed
|
||||||
|
- Smoother than width/height animations
|
||||||
|
|
||||||
|
### Basic Syntax
|
||||||
|
```css
|
||||||
|
clip-path: inset(top right bottom left);
|
||||||
|
clip-path: circle(radius at x y);
|
||||||
|
clip-path: polygon(coordinates);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Image Reveal Effect
|
||||||
|
```css
|
||||||
|
.reveal {
|
||||||
|
clip-path: inset(0 0 100% 0); /* Hidden */
|
||||||
|
animation: reveal 1s forwards cubic-bezier(0.77, 0, 0.175, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes reveal {
|
||||||
|
to { clip-path: inset(0 0 0 0); } /* Fully visible */
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Tab Transitions
|
||||||
|
Duplicate tab lists with different styling. Animate the overlay's clip-path to reveal only the active tab—creates smooth color transitions without timing issues.
|
||||||
|
|
||||||
|
### Scroll-Driven with clip-path
|
||||||
|
```javascript
|
||||||
|
const clipPathY = useTransform(scrollYProgress, [0, 1], ["100%", "0%"]);
|
||||||
|
const motionClipPath = useMotionTemplate`inset(0 0 ${clipPathY} 0)`;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Text Mask Effect
|
||||||
|
Stack elements with complementary clip-paths:
|
||||||
|
```css
|
||||||
|
.top { clip-path: inset(0 0 50% 0); } /* Shows top half */
|
||||||
|
.bottom { clip-path: inset(50% 0 0 0); } /* Shows bottom half */
|
||||||
|
```
|
||||||
|
Adjust values on mouse interaction for seamless transitions.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Button & Interactive Feedback (Emil)
|
||||||
|
|
||||||
|
### Scale on Press
|
||||||
|
Add immediate tactile feedback:
|
||||||
|
```css
|
||||||
|
button:active {
|
||||||
|
transform: scale(0.97);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Don't Animate from scale(0)
|
||||||
|
```jsx
|
||||||
|
// BAD: Unnatural motion
|
||||||
|
initial={{ scale: 0 }}
|
||||||
|
|
||||||
|
// GOOD: Natural, gentle motion
|
||||||
|
initial={{ scale: 0.9, opacity: 0 }}
|
||||||
|
animate={{ scale: 1, opacity: 1 }}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Tooltip Delay Pattern
|
||||||
|
First tooltip in a group: delay + animation. Subsequent tooltips: instant.
|
||||||
|
```css
|
||||||
|
[data-instant] {
|
||||||
|
transition-duration: 0ms;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Blur as a Bridge
|
||||||
|
When state transitions aren't smooth enough, add blur to mask imperfections:
|
||||||
|
```css
|
||||||
|
.transitioning {
|
||||||
|
filter: blur(2px);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. CSS Transitions vs Keyframes (Emil)
|
||||||
|
|
||||||
|
### Interruptibility Problem
|
||||||
|
CSS keyframes can't be interrupted mid-animation. When users rapidly trigger actions, elements "jump" to new positions rather than smoothly retargeting.
|
||||||
|
|
||||||
|
**Solution**: Use CSS transitions with state-driven classes:
|
||||||
|
```jsx
|
||||||
|
useEffect(() => {
|
||||||
|
setMounted(true);
|
||||||
|
}, []);
|
||||||
|
```
|
||||||
|
|
||||||
|
```css
|
||||||
|
.element {
|
||||||
|
transform: translateY(100%);
|
||||||
|
transition: transform 400ms ease;
|
||||||
|
}
|
||||||
|
.element.mounted {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Direct Style Updates for Performance
|
||||||
|
CSS variables cause style recalculation across all children. For frequent updates (drag operations), update styles directly:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// BAD: CSS variable (expensive cascade)
|
||||||
|
element.style.setProperty('--drag-y', `${y}px`);
|
||||||
|
|
||||||
|
// GOOD: Direct style (no cascade)
|
||||||
|
element.style.transform = `translateY(${y}px)`;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Momentum-Based Dismissal
|
||||||
|
Use velocity (distance / time) instead of distance thresholds:
|
||||||
|
```javascript
|
||||||
|
const velocity = dragDistance / elapsedTime;
|
||||||
|
if (velocity > 0.11) dismiss();
|
||||||
|
```
|
||||||
|
|
||||||
|
Fast, short gestures should work—users shouldn't need to drag far.
|
||||||
|
|
||||||
|
### Damping for Natural Boundaries
|
||||||
|
When dragging past boundaries, reduce movement progressively. Things in real life slow down before stopping.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. Spring Physics (Emil)
|
||||||
|
|
||||||
|
### Key Parameters
|
||||||
|
| Parameter | Effect |
|
||||||
|
|-----------|--------|
|
||||||
|
| **Stiffness** | How quickly spring reaches target (higher = faster) |
|
||||||
|
| **Damping** | How quickly oscillations settle (higher = less bounce) |
|
||||||
|
| **Mass** | Weight of object (higher = more momentum) |
|
||||||
|
|
||||||
|
### Spring for Mouse Position
|
||||||
|
```javascript
|
||||||
|
const springConfig = { stiffness: 300, damping: 30 };
|
||||||
|
const x = useSpring(mouseX, springConfig);
|
||||||
|
const y = useSpring(mouseY, springConfig);
|
||||||
|
```
|
||||||
|
|
||||||
|
Use `useSpring` for any value that should interpolate smoothly rather than snap—nothing in the real world changes instantly.
|
||||||
|
|
||||||
|
### Interruptibility
|
||||||
|
Great animations can be interrupted mid-play:
|
||||||
|
- Framer Motion supports interruption natively
|
||||||
|
- CSS transitions allow smooth interruption before completion
|
||||||
|
- Test by clicking rapidly—animations should blend, not queue
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 13. Origin-Aware Animations (Emil)
|
||||||
|
|
||||||
|
Animations should originate from their logical source:
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* Dropdown from button should expand from button, not center */
|
||||||
|
.dropdown {
|
||||||
|
transform-origin: top center;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Component library support:**
|
||||||
|
- Base UI: `--transform-origin` CSS variable
|
||||||
|
- Radix UI: `--radix-dropdown-menu-content-transform-origin`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 14. Scroll-Driven Animations (Jhey)
|
||||||
|
|
||||||
|
### The Core Problem
|
||||||
|
Scroll-driven animations are tied to scroll **speed**. If users scroll slowly, animations play slowly. This feels wrong for most UI—you want animations to trigger at a scroll position, not be controlled by scroll speed.
|
||||||
|
|
||||||
|
### Duration Control Pattern
|
||||||
|
Use two coordinated animations:
|
||||||
|
1. **Trigger animation**: Scroll-driven, toggles a custom property when element enters view
|
||||||
|
2. **Main animation**: Traditional duration-based, activated via Style Query
|
||||||
|
|
||||||
|
This severs the connection between scroll speed and animation timing—the animation runs over a fixed duration once triggered, regardless of how fast the user scrolled.
|
||||||
|
|
||||||
|
### Progressive Enhancement
|
||||||
|
Always provide fallbacks:
|
||||||
|
```javascript
|
||||||
|
// IntersectionObserver fallback for browsers without scroll-driven animation support
|
||||||
|
if (!CSS.supports('animation-timeline', 'scroll()')) {
|
||||||
|
// Use IntersectionObserver instead
|
||||||
|
}
|
||||||
|
```
|
||||||
@ -0,0 +1,489 @@
|
|||||||
|
# Output Format
|
||||||
|
|
||||||
|
This file defines the audit's two output modes:
|
||||||
|
|
||||||
|
- **HTML mode (default)** — a self-contained `.html` file written to the audited project's `motion-audits/` directory and opened in the user's default browser. Each Critical or Important finding gets an auto-looping CSS demo card beside it.
|
||||||
|
- **Terminal mode (flag-triggered)** — the decorated-markdown report rendered inline in the conversation. Use when the user passes `--terminal`, `--inline`, "show the full report inline," "skip the HTML," or any natural-language equivalent. No HTML file is written.
|
||||||
|
|
||||||
|
The two modes contain the same audit content; only the rendering differs. Do not summarize — users want full per-lens perspectives.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## HTML mode
|
||||||
|
|
||||||
|
### File structure
|
||||||
|
|
||||||
|
The HTML output is a single self-contained `.html` document with everything inlined — no external CSS, no external JS, no external fonts (fonts may degrade gracefully if a CDN reference is used). The file scaffolds:
|
||||||
|
|
||||||
|
```
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>{project-name} motion audit — {ISO date}</title>
|
||||||
|
<style>
|
||||||
|
/* 1. Demo shell tokens, .demo-card layout, prefers-reduced-motion */
|
||||||
|
/* — copied from references/demo-shell.html */
|
||||||
|
|
||||||
|
/* 2. Report layout tokens: hero, finding-row, perspective-section,
|
||||||
|
severity-table, recommendation-summary */
|
||||||
|
|
||||||
|
/* 3. Per-finding @keyframes and .demo-card-{n}__motion-target
|
||||||
|
rules, generated per audit — one block per Critical or
|
||||||
|
Important finding, suffixed by the finding's 1-indexed
|
||||||
|
position across the report */
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<!-- Hero -->
|
||||||
|
<!-- Overall Assessment -->
|
||||||
|
<!-- Per-lens sections (primary, secondary, selective) -->
|
||||||
|
<!-- Combined Recommendations tables -->
|
||||||
|
<!-- Lens Reference Summary -->
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Report's own motion posture
|
||||||
|
|
||||||
|
The report itself has **no** entrance, scroll, or mount animations. No staggered reveals, no fade-in-on-scroll, no motion-on-mount outside the demo cards. The demo cards are the only animated elements in the document — anything else would reproduce the AI-slop patterns the skill audits against.
|
||||||
|
|
||||||
|
### Hero header
|
||||||
|
|
||||||
|
Top of the document. Project name + ISO date + severity counts row + primary lens label.
|
||||||
|
|
||||||
|
```html
|
||||||
|
<header class="report-hero">
|
||||||
|
<h1>{project-name} motion audit</h1>
|
||||||
|
<p class="report-hero__date">{ISO date}</p>
|
||||||
|
<p class="report-hero__counts">
|
||||||
|
<a href="#critical-findings">🔴 Critical: {N}</a> ·
|
||||||
|
<a href="#important-findings">🟡 Important: {N}</a> ·
|
||||||
|
<a href="#opportunity-findings">🟢 Opportunities: {N}</a>
|
||||||
|
</p>
|
||||||
|
<p class="report-hero__primary">Primary: {Designer Name} — {Perspective Handle}</p>
|
||||||
|
</header>
|
||||||
|
```
|
||||||
|
|
||||||
|
The severity counts pair each emoji with a text label (`Critical: N`, not just `🔴 N`) so the severity signal is readable under red-green color vision deficiency. Each count is an anchor link to the corresponding section in the body — this is the navigation affordance for long audits with many findings.
|
||||||
|
|
||||||
|
### Overall Assessment
|
||||||
|
|
||||||
|
One short paragraph in larger type. Does this feel polished? Too much? Too little? What's working, what's not?
|
||||||
|
|
||||||
|
```html
|
||||||
|
<section class="report-assessment">
|
||||||
|
<p>{one-paragraph assessment}</p>
|
||||||
|
</section>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Per-lens sections
|
||||||
|
|
||||||
|
Three sections in weighting order: primary, secondary, selective. Each section header pairs the designer name with the perspective handle using an em-dash (`Designer Name — Perspective Handle`):
|
||||||
|
|
||||||
|
```html
|
||||||
|
<section class="perspective-section" id="perspective-emil">
|
||||||
|
<h2>Emil Kowalski — Restraint & Speed</h2>
|
||||||
|
|
||||||
|
<div class="perspective-section__working-well">
|
||||||
|
<h3>What's Working Well</h3>
|
||||||
|
<ul>
|
||||||
|
<li>✓ {observation} — <code>{file.tsx:line}</code></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="perspective-section__issues" id="emil-issues">
|
||||||
|
<h3>Issues to Address</h3>
|
||||||
|
<!-- One .finding-row per Critical or Important finding under this lens -->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="perspective-section__opportunities">
|
||||||
|
<h3>Opportunities</h3>
|
||||||
|
<ul>
|
||||||
|
<li>💡 {idea} — <code>{file.tsx:line}</code></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="perspective-section__quote"><strong>Through Emil's lens:</strong> {1-2 sentence summary}</p>
|
||||||
|
</section>
|
||||||
|
```
|
||||||
|
|
||||||
|
The three perspective handles:
|
||||||
|
|
||||||
|
| Designer | Perspective handle |
|
||||||
|
|---|---|
|
||||||
|
| Emil Kowalski | Restraint & Speed |
|
||||||
|
| Jakub Krehel | Production Polish |
|
||||||
|
| Jhey Tompkins | Experimentation & Delight |
|
||||||
|
|
||||||
|
Always render section headers as `Designer Name — Perspective` (em-dash). Always close each section with the `Through {Designer}'s lens:` summary — it's a documented lens, not a quote from the person.
|
||||||
|
|
||||||
|
### Finding rows (Critical + Important only)
|
||||||
|
|
||||||
|
Each Critical or Important finding inside an `Issues to Address` block renders as a `.finding-row` with the issue prose on the left and the demo card on the right (two-column at desktop, stacked at narrow widths):
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="finding-row" id="finding-{n}">
|
||||||
|
<div class="finding-row__prose">
|
||||||
|
<p class="finding-row__severity">🔴 Critical</p>
|
||||||
|
<h4>{finding title}</h4>
|
||||||
|
<p>{finding explanation}</p>
|
||||||
|
<p class="finding-row__location"><code>{file.tsx:line}</code></p>
|
||||||
|
</div>
|
||||||
|
<article class="demo-card" tabindex="-1">
|
||||||
|
<div class="demo-card__loop-indicator">↻ looping</div>
|
||||||
|
<div class="demo-card__header">{recommended motion title}</div>
|
||||||
|
<div class="demo-card__subhead">{duration} · {easing}</div>
|
||||||
|
<div class="demo-card__stage">
|
||||||
|
<div class="demo-card-{n}__motion-target">{motion target markup}</div>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
`{n}` is the finding's 1-indexed position **across the whole report** (not per-section). This guarantees `@keyframes motion-{n}-...` and `.demo-card-{n}__motion-target` selector names are unique across the document, so concatenating multiple findings' CSS in one `<style>` block does not produce keyframe-name collisions.
|
||||||
|
|
||||||
|
Opportunities never render a demo card. They appear in the per-lens section's `Opportunities` block as a plain bulleted list.
|
||||||
|
|
||||||
|
### Demo-shell embedding pattern
|
||||||
|
|
||||||
|
The agent reads `references/demo-shell.html` and uses it as a template. For each Critical or Important finding:
|
||||||
|
|
||||||
|
1. **Generate the per-finding motion code.** Read the audited code, the relevant lens reference (`emil-kowalski.md`, `jakub-krehel.md`, `jhey-tompkins.md` — matching the lens this finding lives under), and `references/motion-cookbook.md` for the concrete recipe (easing, spring config, enter/exit shape). Write a CSS keyframe block + selector rules that demonstrate the recommended motion. Use the 0% / 66% / 100% cadence with `animation-duration: 3s` (~2s motion, ~1s hold, then loop).
|
||||||
|
|
||||||
|
2. **Inject the per-finding code into the report's `<style>` block.** Append a `@keyframes motion-{n}-...` block and a `.demo-card-{n}__motion-target { animation: ...; }` rule. The shell's CSS variables (`--bg`, `--fg`, `--border`, `--accent`, `--loop-dim`, `--sans`, `--mono`) are available — use them via `var()`. Do not redefine them.
|
||||||
|
|
||||||
|
3. **Inject the motion-target element into the `.demo-card__stage`.** The element's class must be `.demo-card-{n}__motion-target` so it matches the rules from step 2.
|
||||||
|
|
||||||
|
4. **Set the demo card's header and subhead.** Header = short title for the recommended motion (e.g., "Subtle enter: opacity + translateY + blur"). Subhead = duration + easing in monospace (e.g., "300ms · ease-out"). The subhead always renders — populate it for every demo.
|
||||||
|
|
||||||
|
5. **Honor the prefers-reduced-motion guard.** The shell's `@media (prefers-reduced-motion: reduce)` block disables all `[class*="__motion-target"]` animations. The per-finding `@keyframes` 100% values MUST match the motion-target element's default static rendering so the reduce-motion fallback shows the correct final visual. Do not write per-finding overrides inside the reduce-motion block.
|
||||||
|
|
||||||
|
### Combined Recommendations tables
|
||||||
|
|
||||||
|
After the three per-lens sections, render severity-grouped tables for quick scanning:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<section class="recommendations" id="critical-findings">
|
||||||
|
<h2>🔴 Critical (Must Fix)</h2>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr><th>Issue</th><th>File</th><th>Action</th></tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>{issue}</td>
|
||||||
|
<td><code>{file:line}</code></td>
|
||||||
|
<td>{fix}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="recommendations" id="important-findings">
|
||||||
|
<h2>🟡 Important (Should Fix)</h2>
|
||||||
|
<!-- same shape -->
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="recommendations" id="opportunity-findings">
|
||||||
|
<h2>🟢 Opportunities (Could Enhance)</h2>
|
||||||
|
<!-- same shape -->
|
||||||
|
</section>
|
||||||
|
```
|
||||||
|
|
||||||
|
The hero's severity counts link to these section IDs (`#critical-findings`, `#important-findings`, `#opportunity-findings`).
|
||||||
|
|
||||||
|
### Lens Reference Summary (closing)
|
||||||
|
|
||||||
|
```html
|
||||||
|
<section class="reference-summary">
|
||||||
|
<h2>Lens Reference Summary</h2>
|
||||||
|
<p><strong>Which lens was referenced most:</strong> {Designer} — {Perspective}</p>
|
||||||
|
<p><strong>Why:</strong> {one-line context reason}</p>
|
||||||
|
<p><strong>If you want to lean differently:</strong></p>
|
||||||
|
<ul>
|
||||||
|
<li>To follow Emil more strictly: {specific actions}</li>
|
||||||
|
<li>To follow Jakub more strictly: {specific actions}</li>
|
||||||
|
<li>To follow Jhey more strictly: {specific actions}</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Empty-state behavior
|
||||||
|
|
||||||
|
When the audit produces zero Critical + zero Important findings:
|
||||||
|
|
||||||
|
- The hero still renders with the severity counts row (showing `Critical: 0 · Important: 0 · Opportunities: N`).
|
||||||
|
- Each per-lens section's `Issues to Address` block still renders its header, but the body shows a dimmed-italic line:
|
||||||
|
```html
|
||||||
|
<div class="perspective-section__issues" id="emil-issues">
|
||||||
|
<h3>Issues to Address</h3>
|
||||||
|
<p class="perspective-section__empty">No issues found at this severity level.</p>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
Style: `font-style: italic; color: var(--loop-dim); padding: 12px 0;`. Communicates absence without looking broken.
|
||||||
|
- No `.finding-row` markup, no demo cards.
|
||||||
|
- Opportunities still render in text as usual.
|
||||||
|
|
||||||
|
### Responsive behavior
|
||||||
|
|
||||||
|
The `.finding-row` two-column layout (prose left, demo right) needs a breakpoint for narrow viewports:
|
||||||
|
|
||||||
|
```css
|
||||||
|
.finding-row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 360px;
|
||||||
|
gap: 24px;
|
||||||
|
align-items: start;
|
||||||
|
margin: 24px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.finding-row {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
.demo-card {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Below 768px the demo card stacks below the finding prose. The shell's `min-width: 280px` keeps the card from compressing past usability.
|
||||||
|
|
||||||
|
### Report layout tokens
|
||||||
|
|
||||||
|
These extend the shell's tokens for report-level structure. Append to the `<style>` block after the shell's variables:
|
||||||
|
|
||||||
|
```css
|
||||||
|
body {
|
||||||
|
max-width: 960px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 48px 24px;
|
||||||
|
}
|
||||||
|
.report-hero h1 {
|
||||||
|
font-size: 1.75rem;
|
||||||
|
margin: 0 0 4px;
|
||||||
|
}
|
||||||
|
.report-hero__date {
|
||||||
|
font-family: var(--mono);
|
||||||
|
color: var(--loop-dim);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
margin: 0 0 16px;
|
||||||
|
}
|
||||||
|
.report-hero__counts {
|
||||||
|
font-size: 1rem;
|
||||||
|
margin: 0 0 8px;
|
||||||
|
}
|
||||||
|
.report-hero__counts a {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
border-bottom: 1px dashed var(--border);
|
||||||
|
}
|
||||||
|
.report-hero__counts a:hover {
|
||||||
|
border-bottom-style: solid;
|
||||||
|
}
|
||||||
|
.report-hero__primary {
|
||||||
|
font-family: var(--mono);
|
||||||
|
color: var(--loop-dim);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.report-assessment {
|
||||||
|
margin: 32px 0;
|
||||||
|
font-size: 1.05rem;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
.perspective-section {
|
||||||
|
margin: 48px 0;
|
||||||
|
padding-top: 24px;
|
||||||
|
border-top: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
.perspective-section h2 {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
margin: 0 0 16px;
|
||||||
|
}
|
||||||
|
.perspective-section__quote {
|
||||||
|
margin-top: 24px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
background: var(--bg);
|
||||||
|
border-left: 3px solid var(--accent);
|
||||||
|
font-size: 0.95rem;
|
||||||
|
}
|
||||||
|
.recommendations {
|
||||||
|
margin: 48px 0;
|
||||||
|
}
|
||||||
|
.recommendations table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
.recommendations th,
|
||||||
|
.recommendations td {
|
||||||
|
text-align: left;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Terminal mode (flag-triggered fallback)
|
||||||
|
|
||||||
|
When the user passes `--terminal` / `--inline` / a natural-language equivalent, do not write an HTML file. Render the decorated-markdown report inline in the conversation.
|
||||||
|
|
||||||
|
### Quick Summary (Show First)
|
||||||
|
|
||||||
|
```
|
||||||
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
📊 AUDIT SUMMARY
|
||||||
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
🔴 [X] Critical | 🟡 [X] Important | 🟢 [X] Opportunities
|
||||||
|
Primary perspective: [Designer(s)] ([context reason])
|
||||||
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
```
|
||||||
|
|
||||||
|
### Overall Assessment
|
||||||
|
|
||||||
|
One paragraph: Does this feel polished? Too much? Too little? What's working, what's not?
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Per-Designer Sections
|
||||||
|
|
||||||
|
#### Emil's Section
|
||||||
|
|
||||||
|
```
|
||||||
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
⚡ EMIL KOWALSKI — Restraint & Speed
|
||||||
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
```
|
||||||
|
|
||||||
|
*Weight based on context. Heavy for productivity tools, light for creative/kids apps.*
|
||||||
|
|
||||||
|
**What to Check:**
|
||||||
|
- High-frequency interactions that might not need animation
|
||||||
|
- Keyboard-initiated actions that animate (generally shouldn't)
|
||||||
|
- Durations **if this is a productivity context** (Emil prefers under 300ms)
|
||||||
|
- Animations starting from scale(0) (should be 0.9+)
|
||||||
|
- Transform-origin on dropdowns/popovers
|
||||||
|
- CSS keyframes that should be transitions (for interruptibility)
|
||||||
|
|
||||||
|
**Body format:**
|
||||||
|
|
||||||
|
**What's Working Well**
|
||||||
|
- ✓ [Observation] — `file.tsx:line`
|
||||||
|
|
||||||
|
**Issues to Address**
|
||||||
|
- ✗ [Issue] — `file.tsx:line`
|
||||||
|
[Brief explanation]
|
||||||
|
|
||||||
|
**Through Emil's lens**: [1-2 sentence summary]
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### Jakub's Section
|
||||||
|
|
||||||
|
```
|
||||||
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
🎯 JAKUB KREHEL — Production Polish
|
||||||
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
```
|
||||||
|
|
||||||
|
**What to Check:**
|
||||||
|
- Enter animations (opacity + translateY + blur?)
|
||||||
|
- Exit animations (subtler than enters? Or missing entirely?)
|
||||||
|
- **Motion gaps** — Conditional renders without AnimatePresence (from gap analysis)
|
||||||
|
- **Layout transitions** — Size/position changes that snap instead of animate
|
||||||
|
- Shadow vs border usage on varied backgrounds
|
||||||
|
- Optical alignment (buttons with icons, play buttons)
|
||||||
|
- Hover state transitions (150-200ms minimum)
|
||||||
|
- Icon swap animations (opacity + scale + blur)
|
||||||
|
- Spring usage (bounce: 0 for professional, higher for playful)
|
||||||
|
|
||||||
|
**Body format:**
|
||||||
|
|
||||||
|
**What's Working Well**
|
||||||
|
- ✓ [Observation] — `file.tsx:line`
|
||||||
|
|
||||||
|
**Issues to Address**
|
||||||
|
- ✗ [Issue] — `file.tsx:line`
|
||||||
|
[Brief explanation]
|
||||||
|
|
||||||
|
**Through Jakub's lens**: [1-2 sentence summary]
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### Jhey's Section
|
||||||
|
|
||||||
|
```
|
||||||
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
✨ JHEY TOMPKINS — Experimentation & Delight
|
||||||
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
```
|
||||||
|
|
||||||
|
**What to Check:**
|
||||||
|
- Could @property enable smoother animations?
|
||||||
|
- Could linear() provide better easing curves?
|
||||||
|
- Are stagger effects using optimal techniques?
|
||||||
|
- Could scroll-driven animations improve the experience?
|
||||||
|
- What playful touches would enhance engagement?
|
||||||
|
- Are there celebration moments that need more delight? (streaks, achievements, etc.)
|
||||||
|
|
||||||
|
**Body format:**
|
||||||
|
|
||||||
|
**What's Working Well**
|
||||||
|
- ✓ [Observation] — `file.tsx:line`
|
||||||
|
|
||||||
|
**Opportunities**
|
||||||
|
- 💡 [Idea] — `file.tsx:line`
|
||||||
|
[Brief explanation]
|
||||||
|
|
||||||
|
**Through Jhey's lens**: [1-2 sentence summary]
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Combined Recommendations
|
||||||
|
|
||||||
|
**Critical (Must Fix)**
|
||||||
|
| | Issue | File | Action |
|
||||||
|
|-|-------|------|--------|
|
||||||
|
| 🔴 | [Issue] | `file:line` | [Fix] |
|
||||||
|
|
||||||
|
**Important (Should Fix)**
|
||||||
|
| | Issue | File | Action |
|
||||||
|
|-|-------|------|--------|
|
||||||
|
| 🟡 | [Issue] | `file:line` | [Fix] |
|
||||||
|
|
||||||
|
**Opportunities (Could Enhance)**
|
||||||
|
| | Enhancement | Where | Impact |
|
||||||
|
|-|-------------|-------|--------|
|
||||||
|
| 🟢 | [Enhancement] | `file:line` | [Impact] |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Lens Reference Summary
|
||||||
|
|
||||||
|
End every terminal audit with:
|
||||||
|
|
||||||
|
> **Which lens was referenced most**: [Designer Name] — [Perspective]
|
||||||
|
>
|
||||||
|
> **Why**: [Explanation based on the project context]
|
||||||
|
>
|
||||||
|
> **If you want to lean differently**:
|
||||||
|
> - To follow Emil more strictly: [specific actions]
|
||||||
|
> - To follow Jakub more strictly: [specific actions]
|
||||||
|
> - To follow Jhey more strictly: [specific actions]
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Mode selection
|
||||||
|
|
||||||
|
Default to HTML mode. Trigger terminal mode only when the user explicitly signals it via:
|
||||||
|
|
||||||
|
- `--terminal` / `--inline` / `--no-html` flag
|
||||||
|
- Natural-language equivalent: "show the full report inline," "skip the HTML," "no HTML," "terminal only"
|
||||||
|
- Any headless or CI environment where opening a browser doesn't apply
|
||||||
|
|
||||||
|
When in doubt, render HTML and mention the terminal-mode flag in the 3-line summary (see `workflows/audit.md` STEP 3) so the user knows the alternative exists.
|
||||||
@ -0,0 +1,82 @@
|
|||||||
|
# Performance
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## will-change Explained (Jakub)
|
||||||
|
|
||||||
|
A hint to the browser: "I'm about to animate these properties, please prepare."
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* Good - specific properties that will animate */
|
||||||
|
.animated-button {
|
||||||
|
will-change: transform, opacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Bad - too broad, wastes resources */
|
||||||
|
* { will-change: auto; }
|
||||||
|
.element { will-change: all; }
|
||||||
|
```
|
||||||
|
|
||||||
|
**Properties that benefit from will-change**:
|
||||||
|
- transform
|
||||||
|
- opacity
|
||||||
|
- filter (blur, brightness)
|
||||||
|
- clip-path
|
||||||
|
- mask
|
||||||
|
|
||||||
|
**Why it matters**: Without the hint, the browser promotes elements to GPU layers only when animation starts, causing first-frame stutter. With `will-change`, it pre-promotes during idle time.
|
||||||
|
|
||||||
|
**When NOT to use**:
|
||||||
|
- On elements that won't animate
|
||||||
|
- On too many elements (each GPU layer uses memory)
|
||||||
|
- As a "fix" for janky animations (find the real cause)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Gradient Animation Performance (Jakub)
|
||||||
|
|
||||||
|
**Cheap to animate (GPU-accelerated)**:
|
||||||
|
- background-position
|
||||||
|
- background-size
|
||||||
|
- opacity
|
||||||
|
|
||||||
|
**Expensive to animate**:
|
||||||
|
- Color stops
|
||||||
|
- Adding/removing gradient layers
|
||||||
|
- Switching gradient types
|
||||||
|
|
||||||
|
**Tip**: Animate a pseudo-element overlay or use CSS variables that transition indirectly.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Animation Performance Budget
|
||||||
|
|
||||||
|
As a rough guide:
|
||||||
|
- **0-3 elements** with `will-change`: Fine
|
||||||
|
- **4-10 elements**: Careful, test on low-end devices
|
||||||
|
- **10+ elements**: Reconsider approach, use virtualization or stagger
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Properties to Avoid Animating
|
||||||
|
|
||||||
|
These trigger layout recalculation (expensive):
|
||||||
|
- `width`, `height`
|
||||||
|
- `top`, `left`, `right`, `bottom`
|
||||||
|
- `margin`, `padding`
|
||||||
|
- `font-size`
|
||||||
|
|
||||||
|
**Always prefer**:
|
||||||
|
- `transform: translate()` instead of `top`/`left`
|
||||||
|
- `transform: scale()` instead of `width`/`height`
|
||||||
|
- `opacity` for visibility changes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Performance Checklist
|
||||||
|
|
||||||
|
- [ ] `will-change` used sparingly and specifically
|
||||||
|
- [ ] Animations use transform/opacity (not layout properties)
|
||||||
|
- [ ] Tested on low-end devices
|
||||||
|
- [ ] No continuous animations without purpose
|
||||||
|
- [ ] GPU layer count is reasonable (< 10 animated elements)
|
||||||
203
skills-external/design-motion-principles/workflows/audit.md
Normal file
203
skills-external/design-motion-principles/workflows/audit.md
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
# Workflow: Audit Mode
|
||||||
|
|
||||||
|
Review existing motion design and produce a per-designer report. Reconnaissance first, then a full audit, then a structured report. Never apply rules blindly.
|
||||||
|
|
||||||
|
## Required Reading
|
||||||
|
|
||||||
|
Read as you reach each step (not all upfront):
|
||||||
|
1. `references/audit-checklist.md` — your systematic guide (STEP 2)
|
||||||
|
2. The weighted designer file(s) — `emil-kowalski.md`, `jakub-krehel.md`, `jhey-tompkins.md` (STEP 2)
|
||||||
|
3. `references/accessibility.md` — mandatory every audit (STEP 2)
|
||||||
|
4. `references/anti-checklist.md` — the quality gate: AI-slop motion categories + anti-patterns to flag (STEP 2)
|
||||||
|
5. `references/output-format.md` — the report template, HTML mode + terminal mode (STEP 3)
|
||||||
|
6. `references/demo-shell.html` — the demo-card template for HTML-mode per-finding demos (STEP 3)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## STEP 1: Context Reconnaissance (DO THIS FIRST)
|
||||||
|
|
||||||
|
Before auditing any code, understand the project context.
|
||||||
|
|
||||||
|
### Gather Context
|
||||||
|
|
||||||
|
Check these sources:
|
||||||
|
1. **CLAUDE.md** — Any explicit context about the project's purpose or design intent
|
||||||
|
2. **package.json** — What type of app? (Next.js marketing site vs Electron productivity app vs mobile PWA)
|
||||||
|
3. **Existing animations** — Grep for `motion`, `animate`, `transition`, `@keyframes`. What durations are used? What patterns exist?
|
||||||
|
4. **Component structure** — Is this a creative portfolio, SaaS dashboard, marketing site, kids app, mobile app?
|
||||||
|
|
||||||
|
### Motion Gap Analysis (CRITICAL - Don't Skip)
|
||||||
|
|
||||||
|
After finding existing animations, actively search for **missing** animations. These are UI changes that happen without any transition:
|
||||||
|
|
||||||
|
**Search for conditional renders without AnimatePresence:**
|
||||||
|
```bash
|
||||||
|
# Find conditional renders: {condition && <Component />}
|
||||||
|
grep -n "&&\s*(" --include="*.tsx" --include="*.jsx" -r .
|
||||||
|
|
||||||
|
# Find ternary UI swaps: {condition ? <A /> : <B />}
|
||||||
|
grep -n "?\s*<" --include="*.tsx" --include="*.jsx" -r .
|
||||||
|
```
|
||||||
|
|
||||||
|
**For each conditional render found, check:**
|
||||||
|
- Is it wrapped in `<AnimatePresence>`?
|
||||||
|
- Does the component inside have enter/exit animations?
|
||||||
|
- If NO to both → this is a **motion gap** that needs fixing
|
||||||
|
|
||||||
|
**Common motion gap patterns:**
|
||||||
|
- `{isOpen && <Modal />}` — Modal appears/disappears instantly
|
||||||
|
- `{mode === "a" && <ControlsA />}` — Controls swap without transition
|
||||||
|
- `{isLoading ? <Spinner /> : <Content />}` — Loading state snaps
|
||||||
|
- `style={{ height: isExpanded ? 200 : 0 }}` — Height changes without CSS transition
|
||||||
|
- Inline styles with dynamic values but no `transition` property
|
||||||
|
|
||||||
|
**Where to look for motion gaps:**
|
||||||
|
- Inspector/settings panels with mode switches
|
||||||
|
- Conditional form fields
|
||||||
|
- Tab content areas
|
||||||
|
- Expandable/collapsible sections
|
||||||
|
- Toast/notification systems
|
||||||
|
- Loading states
|
||||||
|
- Error states
|
||||||
|
|
||||||
|
### State Your Inference
|
||||||
|
|
||||||
|
After gathering context, tell the user what you found and propose a weighting:
|
||||||
|
|
||||||
|
```
|
||||||
|
## Reconnaissance Complete
|
||||||
|
|
||||||
|
**Project type**: [What you inferred — e.g., "Kids educational app, mobile-first PWA"]
|
||||||
|
**Existing animation style**: [What you observed — e.g., "Spring animations (500-600ms), framer-motion, active:scale patterns"]
|
||||||
|
**Likely intent**: [Your inference — e.g., "Delight and engagement for young children"]
|
||||||
|
|
||||||
|
**Motion gaps found**: [Number] conditional renders without AnimatePresence
|
||||||
|
- [List the files/areas with gaps, e.g., "Settings panel mode switches", "Loading states"]
|
||||||
|
|
||||||
|
**Proposed perspective weighting**:
|
||||||
|
- **Primary**: [Designer] — [Why]
|
||||||
|
- **Secondary**: [Designer] — [Why]
|
||||||
|
- **Selective**: [Designer] — [When applicable]
|
||||||
|
|
||||||
|
Does this approach sound right? Should I adjust the weighting before proceeding with the full audit?
|
||||||
|
```
|
||||||
|
|
||||||
|
Use the Context-to-Perspective Mapping table in SKILL.md to propose the weighting.
|
||||||
|
|
||||||
|
### Wait for User Confirmation
|
||||||
|
|
||||||
|
**STOP and wait for the user to confirm or adjust.** Do not proceed to the full audit until they respond.
|
||||||
|
|
||||||
|
If `AskUserQuestion` is available, present the decision as tappable options:
|
||||||
|
- **Confirm weighting** — Proceed with the proposed primary/secondary/selective designers
|
||||||
|
- **Adjust primary** — Swap which designer is primary (e.g., prioritize delight over restraint)
|
||||||
|
- **Adjust secondary** — Change the secondary lens while keeping primary
|
||||||
|
- **Rebuild weighting** — The project type inference was wrong; start over
|
||||||
|
|
||||||
|
Otherwise ask in plain text: "Does this weighting sound right, or should I adjust?"
|
||||||
|
|
||||||
|
If they adjust (e.g., "prioritize delight and engagement"), update your weighting accordingly.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## STEP 2: Full Audit (After User Confirms)
|
||||||
|
|
||||||
|
Once the user confirms, perform the complete audit by reading the reference files in this order:
|
||||||
|
|
||||||
|
### 2a. Read the Audit Checklist First
|
||||||
|
**Read `references/audit-checklist.md`** — Use this as your systematic guide. It provides the structured checklist of what to evaluate.
|
||||||
|
|
||||||
|
### 2b. Read Designer Files for Your Weighted Perspectives
|
||||||
|
Based on your context weighting, read the relevant designer files:
|
||||||
|
- **Read `references/emil-kowalski.md`** if Emil is primary/secondary — Restraint philosophy, frequency rules, decision frameworks
|
||||||
|
- **Read `references/jakub-krehel.md`** if Jakub is primary/secondary — Production polish philosophy, what to check
|
||||||
|
- **Read `references/jhey-tompkins.md`** if Jhey is primary/secondary — Playful experimentation philosophy, opportunities to surface
|
||||||
|
|
||||||
|
### 2c. Read Topical References as Needed
|
||||||
|
- **Read `references/accessibility.md`** — MANDATORY. Always check for prefers-reduced-motion. No exceptions.
|
||||||
|
- **Read `references/anti-checklist.md`** — Apply this as the audit's quality gate. AI-slop categories at the top (pulsing indicators, hover-scale-on-everything, stagger-spam, etc.) trigger findings; perspective-specific and general anti-patterns sit below. Each category includes a frequency heuristic so single intentional uses don't trip the gate.
|
||||||
|
- **Read `references/performance.md`** — If you see complex animations, check for GPU optimization issues
|
||||||
|
- **Read `references/motion-cookbook.md`** — Reference when making specific implementation recommendations (the recommended fix code, including the per-finding demo motion in HTML mode)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## STEP 3: Output Format (HTML by default)
|
||||||
|
|
||||||
|
The audit produces a **self-contained HTML report** with auto-looping CSS demos beside Critical and Important findings. **Read `references/output-format.md`** for the full template (both HTML mode and terminal mode).
|
||||||
|
|
||||||
|
### Default behavior — write and open the HTML report
|
||||||
|
|
||||||
|
1. **Resolve the write location.** The file is written to `motion-audits/{project-name}-{ISO-date}.html` in the audited project's root.
|
||||||
|
- **Audited project root**: run `git rev-parse --show-toplevel` from the agent's cwd. If it succeeds, use that path. If it fails (no `.git` ancestor), use cwd.
|
||||||
|
- **`{project-name}`**: the `name` field from `package.json` at the project root if it exists; else the `name` field from `pyproject.toml`; else the basename of the project root. Strip any scoping prefix (`@scope/pkg` → `pkg`) and sanitize to lowercase kebab-case (`[a-z0-9-]`, replace others with `-`).
|
||||||
|
- **`{ISO-date}`**: today's date as `YYYY-MM-DD`.
|
||||||
|
- Example: `<project-root>/motion-audits/my-app-2026-05-20.html`.
|
||||||
|
- Do not modify `.gitignore`. The user sees `motion-audits/` in `git status` and decides whether to ignore it.
|
||||||
|
|
||||||
|
2. **Read `references/demo-shell.html`** and use it as the template for each demo card. Embed one card per Critical + Important finding (Opportunities do not get demo cards). Use the suffixed-naming contract — `@keyframes motion-{n}-...` and `.demo-card-{n}__motion-target`, `{n}` = the finding's 1-indexed position across the whole report — so multiple findings don't collide on CSS names.
|
||||||
|
|
||||||
|
3. **Generate per-finding motion code** by reading the audited code, the relevant lens reference, and `references/motion-cookbook.md` for the recipe. Use the shell's 0% / 66% / 100% cadence at `animation-duration: 3s` (~2s motion, ~1s hold, loop). The `@keyframes` 100% state must match the motion-target's default static rendering so the shell's `prefers-reduced-motion` guard shows the correct final visual.
|
||||||
|
|
||||||
|
4. **Write the file.** Create `motion-audits/` if it doesn't exist. Write the complete self-contained HTML document.
|
||||||
|
|
||||||
|
5. **Open in the default browser** via OS-detected Bash dispatch:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
path="<absolute path to the HTML file>"
|
||||||
|
if [ -n "$WSL_DISTRO_NAME" ] || grep -qi microsoft /proc/version 2>/dev/null; then
|
||||||
|
win_path=$(wslpath -w "$path")
|
||||||
|
cmd.exe /c start "" "$win_path" 2>/dev/null
|
||||||
|
else
|
||||||
|
case "$(uname -s)" in
|
||||||
|
Darwin) open "$path" ;;
|
||||||
|
Linux) xdg-open "$path" ;;
|
||||||
|
MINGW*|MSYS*|CYGWIN*) start "" "$path" ;;
|
||||||
|
*) echo "Unknown platform — open this file manually: $path" ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
```
|
||||||
|
|
||||||
|
If the open command returns non-zero or the platform is unrecognized, print `Open this file in your browser: {absolute path}` and continue. Never abort the audit because of a failed browser-open.
|
||||||
|
|
||||||
|
6. **Print the 3-line terminal summary:**
|
||||||
|
|
||||||
|
```
|
||||||
|
🎬 Motion audit complete — 🔴 {N} Critical · 🟡 {N} Important · 🟢 {N} Opportunities
|
||||||
|
📄 Report: {absolute path}
|
||||||
|
💡 Want the full report inline instead? Re-run with --terminal or say "show inline".
|
||||||
|
```
|
||||||
|
|
||||||
|
### Terminal mode (flag-triggered)
|
||||||
|
|
||||||
|
When the user signals terminal mode (`--terminal` / `--inline` / `--no-html` flag, or "show the full report inline" / "skip the HTML" / "terminal only"), **skip the HTML write and the browser-open** and render the decorated-markdown report inline per `references/output-format.md` terminal mode. Do not print the 3-line summary in this case.
|
||||||
|
|
||||||
|
Do not summarize the audit content in either mode — users want full per-lens perspectives.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Agent Gotchas (Self-Check Before Writing the Report)
|
||||||
|
|
||||||
|
Common failure modes during HTML report generation. Most break silently or only manifest when a second finding lands in the same report.
|
||||||
|
|
||||||
|
- **Don't reuse keyframe or class names across findings.** Each demo uses `@keyframes motion-{n}-...` and `.demo-card-{n}__motion-target` where `{n}` is the 1-indexed position across the WHOLE report. Duplicate names mean the second finding shadows the first and the first demo breaks silently.
|
||||||
|
- **Don't redefine the shell's CSS variables.** Per-finding code uses `var(--bg)`, `var(--fg)`, `var(--border)`, `var(--accent)`, `var(--loop-dim)`, `var(--sans)`, `var(--mono)`. Hard-coding colors or fonts breaks dark mode and typography consistency.
|
||||||
|
- **Don't write per-finding overrides inside the `prefers-reduced-motion` block.** The shell's guard collapses all `[class*="__motion-target"]` animations. Make the `@keyframes` 100% state match the motion-target's default static rendering instead.
|
||||||
|
- **Don't include demo cards for Opportunities.** Demos are reserved for Critical and Important. Surface Opportunities in text only.
|
||||||
|
- **Don't animate the report itself.** No entrance, scroll, or mount animations on the report chrome — only the demo cards animate. Animating the report reproduces the AI-slop patterns the audit exists to catch.
|
||||||
|
- **Don't write to cwd if `git rev-parse --show-toplevel` succeeds.** The report goes to `{project-root}/motion-audits/`. Only fall back to cwd when git returns nonzero.
|
||||||
|
- **Don't abort the audit if browser-open fails.** A non-zero exit code is a "no default handler" condition, not an error. Print the path and continue.
|
||||||
|
- **Don't modify `.gitignore`.** The skill never touches it. The user adds `motion-audits/` themselves if they want.
|
||||||
|
- **Don't summarize per-lens findings.** Each section needs its own findings + working-well items + the `Through {Designer}'s lens:` summary.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Success Criteria
|
||||||
|
|
||||||
|
- [ ] Context gathered (CLAUDE.md, package.json, existing animations, structure)
|
||||||
|
- [ ] Motion gap analysis run — conditional renders checked for missing animation
|
||||||
|
- [ ] Weighting proposed and confirmed by the user
|
||||||
|
- [ ] Audit checklist worked through systematically
|
||||||
|
- [ ] Anti-checklist applied — AI-slop categories checked against the codebase
|
||||||
|
- [ ] Accessibility checked — prefers-reduced-motion verified (mandatory)
|
||||||
|
- [ ] HTML report written to `motion-audits/`, opened in browser, 3-line summary printed (or terminal-mode report rendered inline when flagged)
|
||||||
|
- [ ] Report follows output-format.md with full per-lens sections; Critical + Important findings have looping demo cards
|
||||||
87
skills-external/design-motion-principles/workflows/create.md
Normal file
87
skills-external/design-motion-principles/workflows/create.md
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
# Workflow: Create Mode
|
||||||
|
|
||||||
|
Build interactive components with purposeful motion. Light discovery, then generate against the cookbook.
|
||||||
|
|
||||||
|
## Required Reading
|
||||||
|
|
||||||
|
Read before generating:
|
||||||
|
1. `references/motion-cookbook.md` — the recipe source for all motion code
|
||||||
|
2. `references/accessibility.md` — `prefers-reduced-motion` is mandatory in everything you generate
|
||||||
|
3. `references/creation-gotchas.md` — Claude's failure modes when writing motion; self-check against these
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## STEP 1: Light Discovery
|
||||||
|
|
||||||
|
Establish two things — project context and designer weighting — before generating. Keep it to 1-2 questions.
|
||||||
|
|
||||||
|
### Infer First, Ask Second
|
||||||
|
|
||||||
|
Check what you can already see:
|
||||||
|
- **The request** — what component, what interaction, what stack (React / Framer Motion / CSS / HTML)?
|
||||||
|
- **CLAUDE.md, package.json, existing components** — project type and existing animation conventions (durations, easing, libraries)
|
||||||
|
|
||||||
|
### Propose Context + Weighting
|
||||||
|
|
||||||
|
Map the project type to a perspective weighting using the Context-to-Perspective Mapping table in SKILL.md. State your inference in one short block:
|
||||||
|
|
||||||
|
```
|
||||||
|
Building: [what — e.g. "a notification toast, React + Framer Motion"]
|
||||||
|
Project context: [inferred — e.g. "productivity SaaS dashboard"]
|
||||||
|
Proposed weighting: Primary [Designer] · Secondary [Designer]
|
||||||
|
```
|
||||||
|
|
||||||
|
If `AskUserQuestion` is available and the weighting is genuinely ambiguous, offer:
|
||||||
|
- **Confirm** — proceed with the proposed weighting
|
||||||
|
- **Adjust** — change primary/secondary designer
|
||||||
|
|
||||||
|
Otherwise ask in plain text: "Does this weighting sound right, or should I adjust?"
|
||||||
|
|
||||||
|
### Wait Gate
|
||||||
|
|
||||||
|
For non-trivial components, **confirm context before generating**. For a small, well-specified request ("add a press-scale to this button"), state the inference in one line and skip straight to STEP 3 — don't manufacture a question.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## STEP 2: Load Weighted Knowledge
|
||||||
|
|
||||||
|
Based on the confirmed weighting, read the relevant designer file(s):
|
||||||
|
- **Read `references/emil-kowalski.md`** if Emil is primary/secondary — restraint, the frequency rule, when NOT to animate
|
||||||
|
- **Read `references/jakub-krehel.md`** if Jakub is primary/secondary — production polish judgment, subtlety bar
|
||||||
|
- **Read `references/jhey-tompkins.md`** if Jhey is primary/secondary — playful expression, what motion could become
|
||||||
|
|
||||||
|
The designer files give you the **judgment** (should this animate, what feel). The cookbook gives you the **code**.
|
||||||
|
|
||||||
|
If the component involves complex or numerous animations, also read `references/performance.md`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## STEP 3: Generate
|
||||||
|
|
||||||
|
Build the component. Apply, in order:
|
||||||
|
|
||||||
|
1. **The frequency gate (Emil)** — Should this animate at all? High-frequency or keyboard-initiated interactions get minimal or no motion. Decide before adding anything.
|
||||||
|
2. **Recipes from the cookbook** — Use the weighted designer's patterns. Enter = opacity + translateY + blur. Exit subtler than enter. Custom easing or springs, never bare `ease`.
|
||||||
|
3. **Accessibility** — Every animation ships with `prefers-reduced-motion` handling, in the same code. No exceptions, no follow-up.
|
||||||
|
4. **Performance** — Animate `transform` / `opacity` / `filter` only. Never `width` / `height` / `top` / `left`.
|
||||||
|
5. **Context-appropriate timing** — Emil-weighted → under 300ms. Jakub → 200-500ms polish. Jhey → whatever serves the effect.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## STEP 4: Self-Check
|
||||||
|
|
||||||
|
Before presenting, verify the generated code against every item in `references/creation-gotchas.md`. Fix anything that matches a gotcha.
|
||||||
|
|
||||||
|
Then briefly tell the user the motion decisions you made and why — which designer weighting drove the timing, easing, and whether something was deliberately left un-animated.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Success Criteria
|
||||||
|
|
||||||
|
- [ ] Context and weighting confirmed (or inference stated for trivial requests)
|
||||||
|
- [ ] Frequency gate applied — motion is purposeful, not decorative-by-default
|
||||||
|
- [ ] Recipes drawn from the cookbook, matched to the designer weighting
|
||||||
|
- [ ] `prefers-reduced-motion` handled in all generated motion
|
||||||
|
- [ ] Only `transform` / `opacity` / `filter` animated
|
||||||
|
- [ ] Code self-checked against creation-gotchas.md
|
||||||
|
- [ ] Motion decisions explained to the user
|
||||||
@ -242,6 +242,23 @@ else
|
|||||||
info "frontend-design not installed — skipping (run: make plugin)"
|
info "frontend-design not installed — skipping (run: make plugin)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# ── 7.2. Update Design Motion Principles (from GitHub) ──
|
||||||
|
echo ""
|
||||||
|
echo "── Updating Design Motion Principles (kylezantos)..."
|
||||||
|
DMP_DIR="$REPO/skills-external/design-motion-principles"
|
||||||
|
if [ -d "$DMP_DIR" ]; then
|
||||||
|
DMP_TMP="$(mktemp -d)"
|
||||||
|
if git clone --depth 1 https://github.com/kylezantos/design-motion-principles.git "$DMP_TMP" 2>/dev/null; then
|
||||||
|
cp -r "$DMP_TMP/skills/design-motion-principles/"* "$DMP_DIR/"
|
||||||
|
ok "design-motion-principles synced from GitHub"
|
||||||
|
else
|
||||||
|
warn "design-motion-principles: GitHub fetch failed — keeping current version"
|
||||||
|
fi
|
||||||
|
rm -rf "$DMP_TMP"
|
||||||
|
else
|
||||||
|
info "design-motion-principles not installed — skipping"
|
||||||
|
fi
|
||||||
|
|
||||||
# ── 7.4. Update Caveman (hooks + MCP shrink) ──
|
# ── 7.4. Update Caveman (hooks + MCP shrink) ──
|
||||||
# Plugin updates are handled by the marketplace plugin update loop below
|
# Plugin updates are handled by the marketplace plugin update loop below
|
||||||
# (step 8). This step refreshes the standalone hook files (statusline +
|
# (step 8). This step refreshes the standalone hook files (statusline +
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user