mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-06-15 04:31:27 +08:00
Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8fbcce6246 | |||
| 6ba453aa1a | |||
| d7db8c8c83 | |||
| 546a53dc30 | |||
| 43abb6b8f3 | |||
| ab431aacb5 | |||
| 5178539fb0 | |||
| 38ec0e7bde | |||
| 102eaeec2c | |||
| 957d5e72f4 | |||
| 2fa8f59c34 | |||
| b6920b8b30 | |||
| 89c7b25fe1 | |||
| da186abdf1 | |||
| 6907ea0a1a | |||
| daf1d18d51 | |||
| 7b833f0b77 | |||
| 13ffc92877 | |||
| 64d4ab102e |
@@ -1,6 +1,6 @@
|
||||
# Everything Claude Code (ECC) — Agent Instructions
|
||||
|
||||
This is a **production-ready AI coding plugin** providing 48 specialized agents, 182 skills, 68 commands, and automated hook workflows for software development.
|
||||
This is a **production-ready AI coding plugin** providing 48 specialized agents, 185 skills, 68 commands, and automated hook workflows for software development.
|
||||
|
||||
**Version:** 2.0.0-rc.1
|
||||
|
||||
@@ -146,7 +146,7 @@ Troubleshoot failures: check test isolation → verify mocks → fix implementat
|
||||
|
||||
```
|
||||
agents/ — 48 specialized subagents
|
||||
skills/ — 182 workflow skills and domain knowledge
|
||||
skills/ — 185 workflow skills and domain knowledge
|
||||
commands/ — 68 slash commands
|
||||
hooks/ — Trigger-based automations
|
||||
rules/ — Always-follow guidelines (common + per-language)
|
||||
|
||||
@@ -89,7 +89,7 @@ This repo is the raw code only. The guides explain everything.
|
||||
### v2.0.0-rc.1 — Surface Refresh, Operator Workflows, and ECC 2.0 Alpha (Apr 2026)
|
||||
|
||||
- **Dashboard GUI** — New Tkinter-based desktop application (`ecc_dashboard.py` or `npm run dashboard`) with dark/light theme toggle, font customization, and project logo in header and taskbar.
|
||||
- **Public surface synced to the live repo** — metadata, catalog counts, plugin manifests, and install-facing docs now match the actual OSS surface: 48 agents, 182 skills, and 68 legacy command shims.
|
||||
- **Public surface synced to the live repo** — metadata, catalog counts, plugin manifests, and install-facing docs now match the actual OSS surface: 48 agents, 185 skills, and 68 legacy command shims.
|
||||
- **Operator and outbound workflow expansion** — `brand-voice`, `social-graph-ranker`, `connections-optimizer`, `customer-billing-ops`, `ecc-tools-cost-audit`, `google-workspace-ops`, `project-flow-ops`, and `workspace-surface-audit` round out the operator lane.
|
||||
- **Media and launch tooling** — `manim-video`, `remotion-video-creation`, and upgraded social publishing surfaces make technical explainers and launch content part of the same system.
|
||||
- **Framework and product surface growth** — `nestjs-patterns`, richer Codex/OpenCode install surfaces, and expanded cross-harness packaging keep the repo usable beyond Claude Code alone.
|
||||
@@ -350,7 +350,7 @@ If you stacked methods, clean up in this order:
|
||||
/plugin list everything-claude-code@everything-claude-code
|
||||
```
|
||||
|
||||
**That's it!** You now have access to 48 agents, 182 skills, and 68 legacy command shims.
|
||||
**That's it!** You now have access to 48 agents, 185 skills, and 68 legacy command shims.
|
||||
|
||||
### Dashboard GUI
|
||||
|
||||
@@ -1338,7 +1338,7 @@ The configuration is automatically detected from `.opencode/opencode.json`.
|
||||
|---------|-------------|----------|--------|
|
||||
| Agents | PASS: 48 agents | PASS: 12 agents | **Claude Code leads** |
|
||||
| Commands | PASS: 68 commands | PASS: 31 commands | **Claude Code leads** |
|
||||
| Skills | PASS: 182 skills | PASS: 37 skills | **Claude Code leads** |
|
||||
| Skills | PASS: 185 skills | PASS: 37 skills | **Claude Code leads** |
|
||||
| Hooks | PASS: 8 event types | PASS: 11 events | **OpenCode has more!** |
|
||||
| Rules | PASS: 29 rules | PASS: 13 instructions | **Claude Code leads** |
|
||||
| MCP Servers | PASS: 14 servers | PASS: Full | **Full parity** |
|
||||
@@ -1443,7 +1443,7 @@ ECC is the **first plugin to maximize every major AI coding tool**. Here's how e
|
||||
|---------|------------|------------|-----------|----------|
|
||||
| **Agents** | 48 | Shared (AGENTS.md) | Shared (AGENTS.md) | 12 |
|
||||
| **Commands** | 68 | Shared | Instruction-based | 31 |
|
||||
| **Skills** | 182 | Shared | 10 (native format) | 37 |
|
||||
| **Skills** | 185 | Shared | 10 (native format) | 37 |
|
||||
| **Hook Events** | 8 types | 15 types | None yet | 11 types |
|
||||
| **Hook Scripts** | 20+ scripts | 16 scripts (DRY adapter) | N/A | Plugin hooks |
|
||||
| **Rules** | 34 (common + lang) | 34 (YAML frontmatter) | Instruction-based | 13 instructions |
|
||||
|
||||
+1
-1
@@ -160,7 +160,7 @@ Copy-Item -Recurse rules/typescript "$HOME/.claude/rules/"
|
||||
/plugin list everything-claude-code@everything-claude-code
|
||||
```
|
||||
|
||||
**完成!** 你现在可以使用 48 个代理、182 个技能和 68 个命令。
|
||||
**完成!** 你现在可以使用 48 个代理、185 个技能和 68 个命令。
|
||||
|
||||
### multi-* 命令需要额外配置
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Everything Claude Code (ECC) — 智能体指令
|
||||
|
||||
这是一个**生产就绪的 AI 编码插件**,提供 48 个专业代理、182 项技能、68 条命令以及自动化钩子工作流,用于软件开发。
|
||||
这是一个**生产就绪的 AI 编码插件**,提供 48 个专业代理、185 项技能、68 条命令以及自动化钩子工作流,用于软件开发。
|
||||
|
||||
**版本:** 2.0.0-rc.1
|
||||
|
||||
@@ -147,7 +147,7 @@
|
||||
|
||||
```
|
||||
agents/ — 48 个专业子代理
|
||||
skills/ — 182 个工作流技能和领域知识
|
||||
skills/ — 185 个工作流技能和领域知识
|
||||
commands/ — 68 个斜杠命令
|
||||
hooks/ — 基于触发的自动化
|
||||
rules/ — 始终遵循的指导方针(通用 + 每种语言)
|
||||
|
||||
@@ -224,7 +224,7 @@ Copy-Item -Recurse rules/typescript "$HOME/.claude/rules/"
|
||||
/plugin list everything-claude-code@everything-claude-code
|
||||
```
|
||||
|
||||
**搞定!** 你现在可以使用 48 个智能体、182 项技能和 68 个命令了。
|
||||
**搞定!** 你现在可以使用 48 个智能体、185 项技能和 68 个命令了。
|
||||
|
||||
***
|
||||
|
||||
@@ -1134,7 +1134,7 @@ opencode
|
||||
|---------|-------------|----------|--------|
|
||||
| 智能体 | PASS: 48 个 | PASS: 12 个 | **Claude Code 领先** |
|
||||
| 命令 | PASS: 68 个 | PASS: 31 个 | **Claude Code 领先** |
|
||||
| 技能 | PASS: 182 项 | PASS: 37 项 | **Claude Code 领先** |
|
||||
| 技能 | PASS: 185 项 | PASS: 37 项 | **Claude Code 领先** |
|
||||
| 钩子 | PASS: 8 种事件类型 | PASS: 11 种事件 | **OpenCode 更多!** |
|
||||
| 规则 | PASS: 29 条 | PASS: 13 条指令 | **Claude Code 领先** |
|
||||
| MCP 服务器 | PASS: 14 个 | PASS: 完整 | **完全对等** |
|
||||
@@ -1242,7 +1242,7 @@ ECC 是**第一个最大化利用每个主要 AI 编码工具的插件**。以
|
||||
|---------|------------|------------|-----------|----------|
|
||||
| **智能体** | 48 | 共享 (AGENTS.md) | 共享 (AGENTS.md) | 12 |
|
||||
| **命令** | 68 | 共享 | 基于指令 | 31 |
|
||||
| **技能** | 182 | 共享 | 10 (原生格式) | 37 |
|
||||
| **技能** | 185 | 共享 | 10 (原生格式) | 37 |
|
||||
| **钩子事件** | 8 种类型 | 15 种类型 | 暂无 | 11 种类型 |
|
||||
| **钩子脚本** | 20+ 个脚本 | 16 个脚本 (DRY 适配器) | N/A | 插件钩子 |
|
||||
| **规则** | 34 (通用 + 语言) | 34 (YAML 前页) | 基于指令 | 13 条指令 |
|
||||
|
||||
@@ -0,0 +1,575 @@
|
||||
---
|
||||
name: motion-advanced
|
||||
description: Advanced motion patterns for React / Next.js — drag & drop, gestures, text animations, SVG path drawing, custom hooks, imperative sequences (useAnimate), loaders, and the full API decision tree. Requires motion-foundations.
|
||||
version: 1.0
|
||||
tags: [motion, animation, advanced, gestures, svg]
|
||||
category: frontend
|
||||
author: jeff
|
||||
---
|
||||
|
||||
# Motion Advanced
|
||||
|
||||
Complex, interactive, and physics-based animation patterns.
|
||||
Requires `motion-foundations` to be set up first.
|
||||
Use these when `motion-patterns` is not enough.
|
||||
|
||||
## When to Activate
|
||||
|
||||
- Building drag-to-dismiss sheets, swipe gestures, or reorderable lists
|
||||
- Animating text word-by-word, character-by-character, or as a live counter
|
||||
- Drawing SVG paths, morphing icons, or animating circular progress
|
||||
- Writing a custom animation hook (`useScrollReveal`, magnetic button, cursor follower)
|
||||
- Sequencing multi-step animations imperatively with `useAnimate`
|
||||
- Building spinners, shimmer skeletons, pulse indicators, or loading button states
|
||||
|
||||
## Outputs
|
||||
|
||||
This skill produces:
|
||||
|
||||
- Drag interactions: draggable cards, drag-to-dismiss sheets, `Reorder.Group` lists
|
||||
- Gesture hooks: swipe detection, long press, pinch outline
|
||||
- Text animation components: word reveal, character typewriter, number counter
|
||||
- SVG animation: path draw-on, icon morph, stroke progress ring
|
||||
- Custom hooks: `useScrollReveal`, `useHoverScale`, `useNavigationDirection`, `useInViewOnce`
|
||||
- Imperative sequences via `useAnimate` with interrupt-safe `async/await`
|
||||
- Loader components: spinner, shimmer, pulse dot, progress bar, button loading state
|
||||
|
||||
## Principles
|
||||
|
||||
- Physics-based motion (`useSpring`, `springs.*`) always feels more natural than duration-based for direct manipulation.
|
||||
- `useMotionValue` + `useTransform` computes derived values without triggering re-renders.
|
||||
- `useAnimate` sequences are imperative and interrupt-safe — calling `animate()` mid-flight cancels the previous animation automatically.
|
||||
- Motion values (`useMotionValue`, `useSpring`) are SSR-safe and do not cause hydration errors.
|
||||
|
||||
## Rules
|
||||
|
||||
1. **Drag interactions must be tested on touch devices**, not just mouse. `drag` prop works on both but feel and threshold differ.
|
||||
2. **Infinite animations must pause when `document.visibilityState === "hidden"`.** Background tabs must not consume GPU/CPU.
|
||||
3. **Swipe threshold must be explicit.** Never infer intent from velocity alone; combine `offset` + `velocity` checks.
|
||||
4. **`useAnimate` scope ref must be attached to a mounted DOM element.** Calling `animate()` before mount throws silently.
|
||||
5. **Motion values must not be recreated on render.** `useMotionValue(0)` inside a component body is correct; `new MotionValue(0)` in a render is not.
|
||||
6. **All token values are imported from `motion-foundations`.** No inline numbers.
|
||||
7. **Custom hooks must handle cleanup.** Every `window.addEventListener` needs a matching `removeEventListener` in the `useEffect` return.
|
||||
8. **SVG morphing requires equal path command counts.** Paths with different command structures snap instead of interpolating.
|
||||
|
||||
## Decision Guidance
|
||||
|
||||
### Choosing the right advanced API
|
||||
|
||||
| Scenario | API |
|
||||
| ------------------------------ | -------------------------------- |
|
||||
| Drag with physics on release | `drag` + `dragTransition: springs.release` |
|
||||
| Ordered drag-to-reorder list | `Reorder.Group` + `Reorder.Item` |
|
||||
| Dismiss on drag offset | `drag="y"` + `onDragEnd` offset check |
|
||||
| Swipe left/right | `drag="x"` + `onDragEnd` offset check |
|
||||
| Long press | `useLongPress` hook |
|
||||
| Value smoothed over time | `useSpring` |
|
||||
| Value derived from another | `useTransform` |
|
||||
| Multi-step sequence | `useAnimate` with `async/await` |
|
||||
| One-shot imperative animation | `animate()` from `motion` |
|
||||
| Text entering word by word | Stagger on `inline-block` spans |
|
||||
| SVG drawing on | `pathLength` 0 → 1 |
|
||||
| SVG morph | `d` attribute tween (equal commands) |
|
||||
| Circular progress | `strokeDashoffset` tween |
|
||||
|
||||
### When to use `useSpring` vs a spring transition
|
||||
|
||||
| | `useSpring` | `transition: springs.*` |
|
||||
| -------------- | ---------------------------------------- | ----------------------- |
|
||||
| Use for | Cursor follower, pointer-tracked values | Discrete state changes |
|
||||
| Updates | Continuous, on every frame | Triggered by state change |
|
||||
| Interrupt | Smooth — physics picks up from velocity | Restarts from current value |
|
||||
|
||||
## Core Concepts
|
||||
|
||||
### useMotionValue + useTransform
|
||||
|
||||
Reactive computation without re-renders:
|
||||
|
||||
```tsx
|
||||
const x = useMotionValue(0)
|
||||
const opacity = useTransform(x, [-200, 0, 200], [0, 1, 0])
|
||||
// opacity updates every frame as x changes — no setState, no re-render
|
||||
```
|
||||
|
||||
### useAnimate
|
||||
|
||||
Returns `[scope, animate]`. The scope ref must be attached to a DOM element.
|
||||
`animate()` calls are interrupt-safe — calling mid-flight cancels the previous run.
|
||||
|
||||
```tsx
|
||||
const [scope, animate] = useAnimate()
|
||||
|
||||
async function play() {
|
||||
await animate(".step-1", { opacity: 1 }, { duration: 0.3 })
|
||||
await animate(".step-2", { x: 0 }, { duration: 0.4 })
|
||||
animate(".step-3", { scale: 1 }, { duration: 0.25 }) // fire and forget
|
||||
}
|
||||
|
||||
return <div ref={scope}>...</div>
|
||||
```
|
||||
|
||||
## Code Examples
|
||||
|
||||
### Draggable card
|
||||
|
||||
```tsx
|
||||
"use client"
|
||||
import { motion } from "motion/react"
|
||||
import { springs, motionTokens } from "@/lib/motion-tokens"
|
||||
|
||||
<motion.div
|
||||
drag
|
||||
dragConstraints={{ left: -100, right: 100, top: -100, bottom: 100 }}
|
||||
dragElastic={0.1}
|
||||
whileDrag={{
|
||||
scale: motionTokens.scale.pop,
|
||||
boxShadow: "0 16px 40px rgba(0,0,0,0.2)",
|
||||
}}
|
||||
dragTransition={springs.release}
|
||||
/>
|
||||
```
|
||||
|
||||
### Drag-to-dismiss sheet
|
||||
|
||||
```tsx
|
||||
"use client"
|
||||
import { motion, useMotionValue, useTransform } from "motion/react"
|
||||
|
||||
export function BottomSheet({ onClose }: { onClose: () => void }) {
|
||||
const y = useMotionValue(0)
|
||||
const opacity = useTransform(y, [0, 200], [1, 0])
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
drag="y"
|
||||
dragConstraints={{ top: 0 }}
|
||||
style={{ y, opacity }}
|
||||
onDragEnd={(_, info) => {
|
||||
// Rule 3: combine offset + velocity
|
||||
if (info.offset.y > 120 || info.velocity.y > 500) onClose()
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Reorderable list
|
||||
|
||||
```tsx
|
||||
"use client"
|
||||
import { Reorder } from "motion/react"
|
||||
|
||||
export function SortableList() {
|
||||
const [items, setItems] = useState(initialItems)
|
||||
return (
|
||||
<Reorder.Group axis="y" values={items} onReorder={setItems}>
|
||||
{items.map((item) => (
|
||||
<Reorder.Item key={item.id} value={item}>
|
||||
{item.label}
|
||||
</Reorder.Item>
|
||||
))}
|
||||
</Reorder.Group>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Swipe detection
|
||||
|
||||
```tsx
|
||||
"use client"
|
||||
import { motion } from "motion/react"
|
||||
|
||||
const OFFSET_THRESHOLD = 50
|
||||
const VELOCITY_THRESHOLD = 300
|
||||
|
||||
<motion.div
|
||||
drag="x"
|
||||
dragConstraints={{ left: 0, right: 0 }}
|
||||
onDragEnd={(_, info) => {
|
||||
const swipedRight = info.offset.x > OFFSET_THRESHOLD || info.velocity.x > VELOCITY_THRESHOLD
|
||||
const swipedLeft = info.offset.x < -OFFSET_THRESHOLD || info.velocity.x < -VELOCITY_THRESHOLD
|
||||
if (swipedRight) onSwipeRight()
|
||||
if (swipedLeft) onSwipeLeft()
|
||||
}}
|
||||
/>
|
||||
```
|
||||
|
||||
### Long press hook
|
||||
|
||||
```tsx
|
||||
import { useRef } from "react"
|
||||
|
||||
export function useLongPress(callback: () => void, ms = 600) {
|
||||
const timerRef = useRef<ReturnType<typeof setTimeout>>()
|
||||
return {
|
||||
onPointerDown: () => { timerRef.current = setTimeout(callback, ms) },
|
||||
onPointerUp: () => clearTimeout(timerRef.current),
|
||||
onPointerLeave: () => clearTimeout(timerRef.current),
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Word-by-word reveal
|
||||
|
||||
```tsx
|
||||
"use client"
|
||||
import { motion } from "motion/react"
|
||||
import { springs } from "@/lib/motion-tokens"
|
||||
|
||||
export function AnimatedText({ text }: { text: string }) {
|
||||
return (
|
||||
<motion.p
|
||||
variants={{ visible: { transition: { staggerChildren: 0.05 } } }}
|
||||
initial="hidden"
|
||||
animate="visible"
|
||||
>
|
||||
{text.split(" ").map((word, i) => (
|
||||
<motion.span
|
||||
key={i}
|
||||
className="inline-block mr-1"
|
||||
variants={{
|
||||
hidden: { opacity: 0, y: 12 },
|
||||
visible: { opacity: 1, y: 0, transition: springs.gentle },
|
||||
}}
|
||||
>
|
||||
{word}
|
||||
</motion.span>
|
||||
))}
|
||||
</motion.p>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Number counter
|
||||
|
||||
```tsx
|
||||
"use client"
|
||||
import { useRef, useEffect } from "react"
|
||||
import { animate } from "motion"
|
||||
import { motionTokens } from "@/lib/motion-tokens"
|
||||
|
||||
export function Counter({ to }: { to: number }) {
|
||||
const nodeRef = useRef<HTMLSpanElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
const controls = animate(0, to, {
|
||||
duration: motionTokens.duration.crawl,
|
||||
ease: motionTokens.easing.smooth,
|
||||
onUpdate: (v) => {
|
||||
if (nodeRef.current) nodeRef.current.textContent = Math.round(v).toString()
|
||||
},
|
||||
})
|
||||
return controls.stop // Rule 7: cleanup
|
||||
}, [to])
|
||||
|
||||
return <span ref={nodeRef} />
|
||||
}
|
||||
```
|
||||
|
||||
### SVG path draw-on
|
||||
|
||||
```tsx
|
||||
"use client"
|
||||
import { motion } from "motion/react"
|
||||
import { motionTokens } from "@/lib/motion-tokens"
|
||||
|
||||
<motion.path
|
||||
d="M 0 100 Q 50 0 100 100"
|
||||
initial={{ pathLength: 0, opacity: 0 }}
|
||||
animate={{ pathLength: 1, opacity: 1 }}
|
||||
transition={{ duration: motionTokens.duration.slow, ease: "easeInOut" }}
|
||||
/>
|
||||
```
|
||||
|
||||
### Stroke progress ring
|
||||
|
||||
```tsx
|
||||
"use client"
|
||||
import { motion } from "motion/react"
|
||||
import { motionTokens } from "@/lib/motion-tokens"
|
||||
|
||||
const CIRCUMFERENCE = 2 * Math.PI * 40 // r=40
|
||||
|
||||
export function ProgressRing({ progress }: { progress: number }) {
|
||||
return (
|
||||
<svg width="100" height="100" viewBox="0 0 100 100">
|
||||
<circle cx="50" cy="50" r="40" fill="none" stroke="#e5e7eb" strokeWidth="8" />
|
||||
<motion.circle
|
||||
cx="50" cy="50" r="40"
|
||||
fill="none" stroke="#6366f1" strokeWidth="8"
|
||||
strokeLinecap="round"
|
||||
strokeDasharray={CIRCUMFERENCE}
|
||||
animate={{ strokeDashoffset: CIRCUMFERENCE - (progress / 100) * CIRCUMFERENCE }}
|
||||
transition={{ duration: motionTokens.duration.normal, ease: motionTokens.easing.smooth }}
|
||||
style={{ rotate: -90, transformOrigin: "center" }}
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### useScrollReveal hook
|
||||
|
||||
```tsx
|
||||
"use client"
|
||||
import { useRef } from "react"
|
||||
import { useScroll, useTransform } from "motion/react"
|
||||
import { motionTokens } from "@/lib/motion-tokens"
|
||||
|
||||
export function useScrollReveal() {
|
||||
const ref = useRef(null)
|
||||
const { scrollYProgress } = useScroll({ target: ref, offset: ["start end", "end start"] })
|
||||
const opacity = useTransform(scrollYProgress, [0, 0.3], [0, 1])
|
||||
const y = useTransform(scrollYProgress, [0, 0.3], [motionTokens.distance.lg, 0])
|
||||
return { ref, style: { opacity, y } }
|
||||
}
|
||||
|
||||
// Usage
|
||||
const { ref, style } = useScrollReveal()
|
||||
<motion.section ref={ref} style={style} />
|
||||
```
|
||||
|
||||
### Cursor follower
|
||||
|
||||
```tsx
|
||||
"use client"
|
||||
import { useEffect } from "react"
|
||||
import { motion, useMotionValue, useSpring } from "motion/react"
|
||||
|
||||
export function CursorFollower() {
|
||||
const x = useMotionValue(-100)
|
||||
const y = useMotionValue(-100)
|
||||
const sx = useSpring(x, { stiffness: 120, damping: 16 })
|
||||
const sy = useSpring(y, { stiffness: 120, damping: 16 })
|
||||
|
||||
useEffect(() => {
|
||||
const move = (e: MouseEvent) => { x.set(e.clientX); y.set(e.clientY) }
|
||||
window.addEventListener("mousemove", move)
|
||||
return () => window.removeEventListener("mousemove", move) // Rule 7
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
className="fixed top-0 left-0 w-6 h-6 rounded-full bg-indigo-500
|
||||
pointer-events-none -translate-x-1/2 -translate-y-1/2 z-50"
|
||||
style={{ x: sx, y: sy }}
|
||||
/>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Shimmer skeleton
|
||||
|
||||
```tsx
|
||||
"use client"
|
||||
import { motion } from "motion/react"
|
||||
|
||||
export function ShimmerSkeleton({ className }: { className?: string }) {
|
||||
const controls = useAnimation()
|
||||
useEffect(() => {
|
||||
const run = () =>
|
||||
controls.start({ x: ["-100%", "100%"], transition: { repeat: Infinity, duration: 1.2, ease: "linear" } })
|
||||
const onVis = () => (document.visibilityState === "hidden" ? controls.stop() : run())
|
||||
run()
|
||||
document.addEventListener("visibilitychange", onVis)
|
||||
return () => document.removeEventListener("visibilitychange", onVis)
|
||||
}, [controls])
|
||||
return (
|
||||
<div className={`relative overflow-hidden bg-gray-200 rounded ${className}`}>
|
||||
<motion.div
|
||||
className="absolute inset-0 bg-gradient-to-r from-transparent via-white/60 to-transparent"
|
||||
animate={{ x: ["-100%", "100%"] }}
|
||||
transition={{ repeat: Infinity, duration: 1.2, ease: "linear" }}
|
||||
animate={controls}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Button loading state
|
||||
|
||||
```tsx
|
||||
"use client"
|
||||
import { motion, AnimatePresence } from "motion/react"
|
||||
import { motionTokens, springs } from "@/lib/motion-tokens"
|
||||
|
||||
export function LoadingButton({
|
||||
loading,
|
||||
label,
|
||||
onClick,
|
||||
}: {
|
||||
loading: boolean
|
||||
label: string
|
||||
onClick: () => void
|
||||
}) {
|
||||
return (
|
||||
<motion.button
|
||||
onClick={onClick}
|
||||
animate={{ opacity: loading ? 0.7 : 1 }}
|
||||
whileTap={loading ? {} : { scale: motionTokens.scale.press }}
|
||||
transition={springs.snappy}
|
||||
disabled={loading}
|
||||
>
|
||||
<AnimatePresence mode="wait">
|
||||
{loading ? (
|
||||
<motion.span
|
||||
key="loading"
|
||||
initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }}
|
||||
transition={{ duration: motionTokens.duration.fast }}
|
||||
>
|
||||
…
|
||||
</motion.span>
|
||||
) : (
|
||||
<motion.span
|
||||
key="label"
|
||||
initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }}
|
||||
transition={{ duration: motionTokens.duration.fast }}
|
||||
>
|
||||
{label}
|
||||
</motion.span>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</motion.button>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Infinite animation with visibility pause
|
||||
|
||||
```tsx
|
||||
"use client"
|
||||
import { useEffect, useRef } from "react"
|
||||
import { motion, useAnimation } from "motion/react"
|
||||
|
||||
export function PulseDot() {
|
||||
const controls = useAnimation()
|
||||
|
||||
useEffect(() => {
|
||||
const pulse = () =>
|
||||
controls.start({
|
||||
scale: [1, 1.4, 1],
|
||||
opacity: [1, 0.6, 1],
|
||||
transition: { repeat: Infinity, duration: 1.8 },
|
||||
})
|
||||
|
||||
// Rule 2: pause when tab is hidden
|
||||
const handleVisibility = () => {
|
||||
if (document.visibilityState === "hidden") controls.stop()
|
||||
else pulse()
|
||||
}
|
||||
|
||||
pulse()
|
||||
document.addEventListener("visibilitychange", handleVisibility)
|
||||
return () => document.removeEventListener("visibilitychange", handleVisibility) // Rule 7
|
||||
}, [])
|
||||
|
||||
return <motion.span className="w-2 h-2 rounded-full bg-green-400" animate={controls} />
|
||||
}
|
||||
```
|
||||
|
||||
## End-to-End Example
|
||||
|
||||
Drag-to-dismiss sheet with shimmer content, loading state, and reduced motion
|
||||
support — combining `useMotionValue`, `useTransform`, `useSafeMotion`,
|
||||
`AnimatePresence`, and tokens from `motion-foundations`:
|
||||
|
||||
```tsx
|
||||
"use client"
|
||||
import { useState } from "react"
|
||||
import { motion, AnimatePresence, useMotionValue, useTransform } from "motion/react"
|
||||
import { springs, motionTokens } from "@/lib/motion-tokens"
|
||||
import { useSafeMotion } from "@/hooks/use-reduced-motion"
|
||||
import { ShimmerSkeleton } from "./shimmer-skeleton"
|
||||
|
||||
export function DismissibleSheet({
|
||||
isOpen,
|
||||
onClose,
|
||||
loading,
|
||||
children,
|
||||
}: {
|
||||
isOpen: boolean
|
||||
onClose: () => void
|
||||
loading: boolean
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
const safe = useSafeMotion(motionTokens.distance.xl)
|
||||
const y = useMotionValue(0)
|
||||
const opacity = useTransform(y, [0, 200], [1, 0])
|
||||
|
||||
return (
|
||||
<AnimatePresence>
|
||||
{isOpen && (
|
||||
<>
|
||||
{/* Backdrop */}
|
||||
<motion.div
|
||||
key="backdrop"
|
||||
className="fixed inset-0 bg-black/40"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
onClick={onClose}
|
||||
/>
|
||||
|
||||
{/* Sheet — drag-to-dismiss */}
|
||||
<motion.div
|
||||
key="sheet"
|
||||
className="fixed bottom-0 inset-x-0 rounded-t-2xl bg-white p-6"
|
||||
drag="y"
|
||||
dragConstraints={{ top: 0 }}
|
||||
style={{ y, opacity }}
|
||||
onDragEnd={(_, info) => {
|
||||
if (info.offset.y > 120 || info.velocity.y > 500) onClose()
|
||||
}}
|
||||
initial={safe.initial}
|
||||
animate={safe.animate}
|
||||
exit={safe.exit}
|
||||
transition={springs.gentle}
|
||||
>
|
||||
{loading ? (
|
||||
<div className="space-y-3">
|
||||
<ShimmerSkeleton className="h-4 w-3/4" />
|
||||
<ShimmerSkeleton className="h-4 w-1/2" />
|
||||
<ShimmerSkeleton className="h-20 w-full" />
|
||||
</div>
|
||||
) : children}
|
||||
</motion.div>
|
||||
</>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Constraints / Non-Goals
|
||||
|
||||
This skill does **not** cover:
|
||||
|
||||
- Token and spring definitions → see `motion-foundations`
|
||||
- Standard UI patterns (button, modal, stagger, page transitions) → see `motion-patterns`
|
||||
- CSS-only animations or Tailwind `animate-*` without `motion/react`
|
||||
- Canvas or WebGL-based animation (Three.js, Pixi, etc.)
|
||||
- Full drag-and-drop systems with external state managers (dnd-kit, react-beautiful-dnd)
|
||||
- Game-loop or frame-by-frame animation
|
||||
|
||||
## Anti-Patterns
|
||||
|
||||
| Anti-pattern | Rule violated | Fix |
|
||||
| ---------------------------------------------- | ------- | ------------------------------------------------ |
|
||||
| `drag` tested only on desktop | Rule 1 | Test on touch emulator and real device |
|
||||
| `animate={{ repeat: Infinity }}` with no pause | Rule 2 | Add `visibilitychange` listener |
|
||||
| `onDragEnd` checking only offset, not velocity | Rule 3 | Check both `info.offset` and `info.velocity` |
|
||||
| `animate(scope, ...)` before `useEffect` | Rule 4 | Call `animate()` only after mount |
|
||||
| `const x = new MotionValue(0)` in render | Rule 5 | Use `const x = useMotionValue(0)` |
|
||||
| `transition={{ duration: 1.2 }}` inline | Rule 6 | Use `motionTokens.duration.crawl` |
|
||||
| `useEffect` without cleanup | Rule 7 | Return `removeEventListener` / `controls.stop` |
|
||||
| SVG morph between paths with different commands | Rule 8 | Normalize path commands before animating |
|
||||
|
||||
## Related Skills
|
||||
|
||||
- **`motion-foundations`** — defines all tokens, springs, `useSafeMotion`, and SSR guards imported here. Must be set up before using this skill.
|
||||
- **`motion-patterns`** — handles standard UI patterns (button, modal, stagger, page transitions, scroll reveals). Use it before reaching for the advanced patterns here.
|
||||
@@ -0,0 +1,291 @@
|
||||
---
|
||||
name: motion-foundations
|
||||
description: Motion tokens, spring presets, performance rules, device adaptation, accessibility enforcement, and SSR safety for React / Next.js using motion/react. Foundation layer — all other motion skills depend on this.
|
||||
version: 1.0
|
||||
tags: [motion, animation, performance, accessibility]
|
||||
category: frontend
|
||||
author: jeff
|
||||
---
|
||||
|
||||
# Motion Foundations
|
||||
|
||||
The base layer of the motion system. Defines every value, constraint, and
|
||||
rule that downstream skills (`motion-patterns`, `motion-advanced`) inherit.
|
||||
Load this skill before any animation work begins.
|
||||
|
||||
## When to Activate
|
||||
|
||||
- Starting any animated component from scratch
|
||||
- Setting up tokens, spring presets, or easing values
|
||||
- Implementing `prefers-reduced-motion` support
|
||||
- Debugging hydration mismatches from animation initial states
|
||||
- Evaluating whether an animation should exist at all
|
||||
|
||||
## Outputs
|
||||
|
||||
This skill produces:
|
||||
|
||||
- A shared `motionTokens` object (duration, easing, distance, scale)
|
||||
- A shared `springs` preset map (5 named configs)
|
||||
- A `shouldAnimate` boolean gate used by all components
|
||||
- Accessibility-compliant animation defaults via `useReducedMotion`
|
||||
- SSR-safe initial states with zero hydration warnings
|
||||
|
||||
## Principles
|
||||
|
||||
Motion must do at least one of the following or it must be removed:
|
||||
|
||||
- Guide attention
|
||||
- Communicate state
|
||||
- Preserve spatial continuity
|
||||
|
||||
Responsiveness always outranks smoothness. A 60 fps animation that causes
|
||||
input delay is worse than no animation.
|
||||
|
||||
## Rules
|
||||
|
||||
These are non-negotiable. They apply to every component in the system.
|
||||
|
||||
1. **Use `motion/react` only.** Never import from `framer-motion`. Never mix the two in the same tree.
|
||||
2. **`initial` must match server output.** If the server renders `opacity: 1`, the `initial` prop must also be `opacity: 1`. No exceptions.
|
||||
3. **Reduced motion overrides everything.** When `useReducedMotion()` returns `true` or `prefersReduced` is `true`, all transforms are disabled. Opacity-only fades at ≤ 0.2s are the only permitted fallback.
|
||||
4. **Never animate layout properties.** `width`, `height`, `top`, `left`, `margin`, `padding` are banned from `animate`. Use `transform` and `opacity` only.
|
||||
5. **All token values come from `motionTokens`.** Hardcoded durations and easings in component files are forbidden.
|
||||
6. **All spring configs come from the `springs` map.** Inline `stiffness`/`damping` values are forbidden.
|
||||
7. **`"use client"` is required** on every file that imports from `motion/react`.
|
||||
8. **Never read `window` or `navigator` at module level.** Always guard with `typeof window !== "undefined"`.
|
||||
|
||||
## Decision Guidance
|
||||
|
||||
### Choosing a duration
|
||||
|
||||
| Token | Use when |
|
||||
| --------- | -------------------------------------------- |
|
||||
| `instant` | Tooltip show/hide, focus ring, badge update |
|
||||
| `fast` | Button feedback, icon swap, chip toggle |
|
||||
| `normal` | Modal open, card expand, page element enter |
|
||||
| `slow` | Hero entrance, full-page transition |
|
||||
| `crawl` | Deliberate storytelling; use sparingly |
|
||||
|
||||
### Choosing a spring
|
||||
|
||||
| Preset | Use when |
|
||||
| --------- | ------------------------------------------ |
|
||||
| `snappy` | Default UI — buttons, chips, nav items |
|
||||
| `gentle` | Cards, modals, panels landing softly |
|
||||
| `bouncy` | Playful moments — empty states, onboarding |
|
||||
| `instant` | Tooltips, popovers, dropdowns |
|
||||
| `release` | Drag release — natural physics feel |
|
||||
|
||||
### When to disable animation entirely
|
||||
|
||||
Disable (set `shouldAnimate = false`) when:
|
||||
|
||||
- `prefersReduced` is `true`
|
||||
- `isLowEnd` is `true` and the animation is non-essential
|
||||
- The element is off-screen and will never enter the viewport
|
||||
- The animation is purely decorative with no UX purpose
|
||||
|
||||
## Core Concepts
|
||||
|
||||
### Token system
|
||||
|
||||
```ts
|
||||
// lib/motion-tokens.ts
|
||||
export const motionTokens = {
|
||||
duration: {
|
||||
instant: 0.08,
|
||||
fast: 0.18,
|
||||
normal: 0.35,
|
||||
slow: 0.6,
|
||||
crawl: 1.0,
|
||||
},
|
||||
easing: {
|
||||
smooth: [0.22, 1, 0.36, 1],
|
||||
sharp: [0.4, 0, 0.2, 1],
|
||||
bounce: [0.34, 1.56, 0.64, 1],
|
||||
linear: [0, 0, 1, 1],
|
||||
},
|
||||
distance: {
|
||||
xs: 4,
|
||||
sm: 8,
|
||||
md: 16,
|
||||
lg: 24,
|
||||
xl: 48,
|
||||
},
|
||||
scale: {
|
||||
subtle: 0.98,
|
||||
press: 0.95,
|
||||
pop: 1.04,
|
||||
},
|
||||
}
|
||||
|
||||
export const springs = {
|
||||
snappy: { type: "spring", stiffness: 300, damping: 30 },
|
||||
gentle: { type: "spring", stiffness: 120, damping: 14 },
|
||||
bouncy: { type: "spring", stiffness: 400, damping: 10 },
|
||||
instant: { type: "spring", stiffness: 600, damping: 35 },
|
||||
release: { type: "spring", stiffness: 200, damping: 20, restDelta: 0.001 },
|
||||
}
|
||||
```
|
||||
|
||||
### Runtime flags
|
||||
|
||||
```ts
|
||||
// lib/motion-config.ts
|
||||
export const motionConfig = {
|
||||
isLowEnd:
|
||||
typeof navigator !== "undefined" &&
|
||||
navigator.hardwareConcurrency <= 4,
|
||||
|
||||
prefersReduced:
|
||||
typeof window !== "undefined" &&
|
||||
window.matchMedia("(prefers-reduced-motion: reduce)").matches,
|
||||
|
||||
get shouldAnimate() {
|
||||
return !this.prefersReduced
|
||||
},
|
||||
|
||||
get duration() {
|
||||
return this.isLowEnd || this.prefersReduced
|
||||
? motionTokens.duration.instant
|
||||
: motionTokens.duration.normal
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### Accessibility
|
||||
|
||||
**Priority order (highest to lowest):**
|
||||
|
||||
1. `prefers-reduced-motion: reduce` — disables all transforms, limits opacity transitions to ≤ 0.2s
|
||||
2. Low-end device detection — reduces duration, removes non-essential animations
|
||||
3. Design preference — everything else
|
||||
|
||||
Motion must degrade gracefully. It must never disappear abruptly in a way
|
||||
that causes layout shift or confuses orientation.
|
||||
|
||||
```tsx
|
||||
// hooks/use-reduced-motion.tsx
|
||||
"use client"
|
||||
import { useReducedMotion } from "motion/react"
|
||||
|
||||
export function useSafeMotion(fullY: number = 16) {
|
||||
const reduce = useReducedMotion()
|
||||
return {
|
||||
initial: { opacity: 0, y: reduce ? 0 : fullY },
|
||||
animate: { opacity: 1, y: 0 },
|
||||
exit: { opacity: 0, y: reduce ? 0 : -fullY },
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```css
|
||||
/* globals.css */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.motion-safe-transition { transition: opacity 0.15s; }
|
||||
.motion-reduce-transform { transform: none !important; }
|
||||
}
|
||||
```
|
||||
|
||||
```html
|
||||
<!-- Tailwind -->
|
||||
<div class="motion-safe:animate-fade motion-reduce:opacity-100"></div>
|
||||
```
|
||||
|
||||
### SSR / hydration safety
|
||||
|
||||
**Rule: `initial` must always match what the server renders.**
|
||||
|
||||
```tsx
|
||||
// WRONG — server renders opacity:1 but initial says 0 → hydration mismatch
|
||||
<motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} />
|
||||
|
||||
// CORRECT — use AnimatePresence or defer to client mount
|
||||
"use client"
|
||||
const [mounted, setMounted] = useState(false)
|
||||
useEffect(() => setMounted(true), [])
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: mounted ? 0 : 1 }}
|
||||
animate={{ opacity: 1 }}
|
||||
/>
|
||||
```
|
||||
|
||||
## Code Examples
|
||||
|
||||
### End-to-end: tokens + springs + accessibility + SSR guard
|
||||
|
||||
```tsx
|
||||
// components/fade-in-card.tsx
|
||||
"use client"
|
||||
|
||||
import { useState, useEffect } from "react"
|
||||
import { motion } from "motion/react"
|
||||
import { motionTokens, springs } from "@/lib/motion-tokens"
|
||||
import { useSafeMotion } from "@/hooks/use-reduced-motion"
|
||||
import { motionConfig } from "@/lib/motion-config"
|
||||
|
||||
interface FadeInCardProps {
|
||||
children: React.ReactNode
|
||||
delay?: number
|
||||
}
|
||||
|
||||
export function FadeInCard({ children, delay = 0 }: FadeInCardProps) {
|
||||
// SSR guard — initial must match server output (opacity: 1)
|
||||
const [mounted, setMounted] = useState(false)
|
||||
useEffect(() => setMounted(true), [])
|
||||
|
||||
// Accessibility — disables transform when reduced motion is preferred
|
||||
const safeMotion = useSafeMotion(motionTokens.distance.md)
|
||||
|
||||
// Device gate — skip animation on low-end hardware
|
||||
if (!motionConfig.shouldAnimate || !mounted) {
|
||||
return <div>{children}</div>
|
||||
}
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
initial={safeMotion.initial}
|
||||
animate={safeMotion.animate}
|
||||
exit={safeMotion.exit}
|
||||
transition={{
|
||||
...springs.gentle,
|
||||
delay,
|
||||
}}
|
||||
whileHover={{ scale: motionTokens.scale.pop }}
|
||||
whileTap={{ scale: motionTokens.scale.press }}
|
||||
>
|
||||
{children}
|
||||
</motion.div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Constraints / Non-Goals
|
||||
|
||||
This skill does **not** cover:
|
||||
|
||||
- UI component patterns (button, modal, stagger) → see `motion-patterns`
|
||||
- Drag, gestures, SVG, text animations, custom hooks → see `motion-advanced`
|
||||
- CSS-only animations or Tailwind `animate-*` classes without `motion/react`
|
||||
- Third-party animation libraries (GSAP, anime.js, etc.)
|
||||
- Motion design decisions (when to animate, what to emphasize) — that is a design concern, not a code constraint
|
||||
|
||||
## Anti-Patterns
|
||||
|
||||
| Anti-pattern | Rule violated | Fix |
|
||||
| --------------------------------------- | ------- | ------------------------------- |
|
||||
| `import { motion } from "framer-motion"` | Rule 1 | Use `motion/react` |
|
||||
| `initial={{ opacity: 0 }}` on SSR component | Rule 2 | Add mount guard |
|
||||
| Skipping `useReducedMotion` check | Rule 3 | Use `useSafeMotion` hook |
|
||||
| `animate={{ width: "100%" }}` | Rule 4 | Use `scaleX` transform instead |
|
||||
| `transition={{ duration: 0.4 }}` inline | Rule 5 | Use `motionTokens.duration.normal` |
|
||||
| `{ stiffness: 300, damping: 30 }` inline | Rule 6 | Use `springs.snappy` |
|
||||
| Missing `"use client"` directive | Rule 7 | Add to top of file |
|
||||
| `navigator.hardwareConcurrency` at module level | Rule 8 | Wrap in `typeof navigator !== "undefined"` |
|
||||
|
||||
## Related Skills
|
||||
|
||||
- **`motion-patterns`** — consumes tokens and springs defined here to build button, modal, stagger, page transition, and scroll patterns. Does not redefine any values.
|
||||
- **`motion-advanced`** — consumes tokens and springs defined here for drag, SVG, text, and gesture patterns. Adds `useAnimate` sequences and custom hooks on top of this foundation.
|
||||
@@ -0,0 +1,419 @@
|
||||
---
|
||||
name: motion-patterns
|
||||
description: Production-ready animation patterns for React / Next.js — button, modal, toast, stagger, page transitions, exit animations, scroll, and layout — built on motion-foundations tokens and springs.
|
||||
version: 1.0
|
||||
tags: [motion, animation, ui-patterns]
|
||||
category: frontend
|
||||
author: jeff
|
||||
---
|
||||
|
||||
# Motion Patterns
|
||||
|
||||
Copy-paste patterns for the most common UI animation needs.
|
||||
Every pattern here is built on `motion-foundations` tokens and springs.
|
||||
Do not define new duration or easing values here — import them.
|
||||
|
||||
## When to Activate
|
||||
|
||||
- Animating a button, card, modal, or toast notification
|
||||
- Building list entrances with stagger
|
||||
- Setting up page transitions in Next.js App Router
|
||||
- Adding entrance or exit animations to conditional content
|
||||
- Implementing scroll-reveal, scroll-linked progress, or sticky story sections
|
||||
- Building expanding cards, accordions, or shared-element transitions
|
||||
|
||||
## Outputs
|
||||
|
||||
This skill produces:
|
||||
|
||||
- Accessible, SSR-safe animation for all standard UI components
|
||||
- `AnimatePresence`-wrapped conditional renders with correct exit behavior
|
||||
- Page transition wrapper component for Next.js App Router
|
||||
- Scroll-reveal and scroll-linked patterns using `useScroll` + `useTransform`
|
||||
- Layout animation patterns (`layout`, `layoutId`) for expanding and crossfading elements
|
||||
|
||||
## Principles
|
||||
|
||||
- Every pattern imports from `motion-foundations`. No raw numbers.
|
||||
- Every conditional render is wrapped in `AnimatePresence` with a `key`.
|
||||
- Exit animations are always defined alongside enter animations — never as an afterthought.
|
||||
- `layout` is used only for small, isolated shifts. Large subtrees get explicit transforms.
|
||||
|
||||
## Rules
|
||||
|
||||
1. **Always wrap conditional renders in `AnimatePresence` with a `key`** on the direct child. Without a key, exit animations never fire.
|
||||
2. **Always define `exit` when defining `initial` + `animate`.** An animation without an exit is incomplete.
|
||||
3. **Use `mode="wait"` on page transitions.** Enter must not start until exit completes.
|
||||
4. **Never use `layout` on subtrees with more than ~5 children or deeply nested DOM.** Use explicit `x`/`y` transforms instead.
|
||||
5. **Stagger interval must stay between `0.05s` and `0.10s`.** Below feels mechanical; above feels sluggish.
|
||||
6. **Modals must always include:** focus trap, Escape-key close, scroll lock, `role="dialog"`, `aria-modal="true"`.
|
||||
7. **Scroll reveals use `viewport={{ once: true }}`.** Repeating on scroll-out is distracting, not informative.
|
||||
8. **All token values are imported from `motion-foundations`.** No inline numbers.
|
||||
|
||||
## Decision Guidance
|
||||
|
||||
### Choosing the right pattern
|
||||
|
||||
| Situation | Pattern |
|
||||
| ---------------------------------------- | ---------------------- |
|
||||
| Element appears / disappears | `AnimatePresence` |
|
||||
| List of items loading in sequence | Stagger variants |
|
||||
| Navigating between routes | Page transition wrapper|
|
||||
| Element changes size in place | `layout` prop |
|
||||
| Same element moves across page contexts | `layoutId` |
|
||||
| Element enters when scrolled into view | `whileInView` |
|
||||
| Value tied to scroll position | `useScroll` + `useTransform` |
|
||||
|
||||
### When to use `mode="wait"` vs `mode="sync"`
|
||||
|
||||
| Mode | Use when |
|
||||
| ------- | --------------------------------------- |
|
||||
| `wait` | Page transitions, content swaps (one at a time) |
|
||||
| `sync` | Stacked notifications, list items (overlap is fine) |
|
||||
| `popLayout` | Items removed from a reflow list |
|
||||
|
||||
## Core Concepts
|
||||
|
||||
### AnimatePresence contract
|
||||
|
||||
Three things must always be true:
|
||||
|
||||
1. `AnimatePresence` wraps the conditional
|
||||
2. The direct child has a `key`
|
||||
3. The child has an `exit` prop
|
||||
|
||||
Miss any one of these and the exit animation silently fails.
|
||||
|
||||
### layout vs layoutId
|
||||
|
||||
- `layout` — animates the element's own size/position change in place
|
||||
- `layoutId` — links two separate elements, crossfading between them across renders
|
||||
|
||||
Use `layout="position"` on text inside an expanding container to prevent text reflow from animating.
|
||||
|
||||
## Code Examples
|
||||
|
||||
### Button feedback
|
||||
|
||||
```tsx
|
||||
"use client"
|
||||
import { motion } from "motion/react"
|
||||
import { springs, motionTokens } from "@/lib/motion-tokens"
|
||||
|
||||
<motion.button
|
||||
whileHover={{ scale: motionTokens.scale.pop }}
|
||||
whileTap={{ scale: motionTokens.scale.press }}
|
||||
transition={springs.snappy}
|
||||
/>
|
||||
```
|
||||
|
||||
### Stagger list
|
||||
|
||||
```tsx
|
||||
"use client"
|
||||
import { motion } from "motion/react"
|
||||
import { motionTokens, springs } from "@/lib/motion-tokens"
|
||||
|
||||
const container = {
|
||||
hidden: {},
|
||||
visible: {
|
||||
transition: {
|
||||
staggerChildren: 0.08, // within the 0.05–0.10 rule
|
||||
delayChildren: 0.1,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const item = {
|
||||
hidden: { opacity: 0, y: motionTokens.distance.md },
|
||||
visible: { opacity: 1, y: 0, transition: springs.gentle },
|
||||
}
|
||||
|
||||
<motion.ul variants={container} initial="hidden" animate="visible">
|
||||
{items.map((i) => (
|
||||
<motion.li key={i.id} variants={item} />
|
||||
))}
|
||||
</motion.ul>
|
||||
```
|
||||
|
||||
### Modal
|
||||
|
||||
```tsx
|
||||
"use client"
|
||||
import { motion, AnimatePresence } from "motion/react"
|
||||
import { springs } from "@/lib/motion-tokens"
|
||||
|
||||
// Wrap at the call site:
|
||||
// <AnimatePresence>{isOpen && <Modal key="modal" />}</AnimatePresence>
|
||||
|
||||
export function Modal({ onClose }: { onClose: () => void }) {
|
||||
return (
|
||||
<>
|
||||
{/* Overlay */}
|
||||
<motion.div
|
||||
className="fixed inset-0 bg-black/50"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
onClick={onClose}
|
||||
/>
|
||||
|
||||
{/* Panel — accessibility requirements: focus trap, Escape close,
|
||||
scroll lock, role="dialog", aria-modal="true" */}
|
||||
<motion.div
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
className="fixed inset-x-4 top-1/2 -translate-y-1/2 rounded-xl bg-white p-6"
|
||||
initial={{ opacity: 0, scale: 0.95, y: 8 }}
|
||||
animate={{ opacity: 1, scale: 1, y: 0 }}
|
||||
exit={{ opacity: 0, scale: 0.95, y: 8 }}
|
||||
transition={springs.gentle}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Toast stack
|
||||
|
||||
```tsx
|
||||
"use client"
|
||||
import { motion, AnimatePresence } from "motion/react"
|
||||
import { springs } from "@/lib/motion-tokens"
|
||||
|
||||
<AnimatePresence mode="sync">
|
||||
{toasts.map((t) => (
|
||||
<motion.div
|
||||
key={t.id}
|
||||
layout
|
||||
initial={{ opacity: 0, x: 48, scale: 0.9 }}
|
||||
animate={{ opacity: 1, x: 0, scale: 1 }}
|
||||
exit={{ opacity: 0, x: 48, scale: 0.9 }}
|
||||
transition={springs.snappy}
|
||||
/>
|
||||
))}
|
||||
</AnimatePresence>
|
||||
```
|
||||
|
||||
### Page transition (Next.js App Router)
|
||||
|
||||
```tsx
|
||||
// components/page-transition.tsx
|
||||
"use client"
|
||||
import { motion, AnimatePresence } from "motion/react"
|
||||
import { usePathname } from "next/navigation"
|
||||
import { motionTokens } from "@/lib/motion-tokens"
|
||||
|
||||
const variants = {
|
||||
initial: { opacity: 0, y: motionTokens.distance.sm },
|
||||
enter: { opacity: 1, y: 0 },
|
||||
exit: { opacity: 0, y: -motionTokens.distance.sm },
|
||||
}
|
||||
|
||||
export function PageTransition({ children }: { children: React.ReactNode }) {
|
||||
const pathname = usePathname()
|
||||
return (
|
||||
<AnimatePresence mode="wait">
|
||||
<motion.div
|
||||
key={pathname}
|
||||
variants={variants}
|
||||
initial="initial"
|
||||
animate="enter"
|
||||
exit="exit"
|
||||
transition={{
|
||||
duration: motionTokens.duration.normal,
|
||||
ease: motionTokens.easing.smooth,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</motion.div>
|
||||
</AnimatePresence>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Scroll reveal
|
||||
|
||||
```tsx
|
||||
"use client"
|
||||
import { motion } from "motion/react"
|
||||
import { motionTokens, springs } from "@/lib/motion-tokens"
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: motionTokens.distance.lg }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true, margin: "-80px" }} // once: true — rule 7
|
||||
transition={{ duration: motionTokens.duration.slow, ease: motionTokens.easing.smooth }}
|
||||
/>
|
||||
```
|
||||
|
||||
### Scroll progress bar
|
||||
|
||||
```tsx
|
||||
"use client"
|
||||
import { motion, useScroll } from "motion/react"
|
||||
|
||||
export function ScrollProgress() {
|
||||
const { scrollYProgress } = useScroll()
|
||||
return (
|
||||
<motion.div
|
||||
className="fixed top-0 left-0 h-1 bg-indigo-500 origin-left w-full"
|
||||
style={{ scaleX: scrollYProgress }}
|
||||
/>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Expanding card
|
||||
|
||||
```tsx
|
||||
"use client"
|
||||
import { useState } from "react"
|
||||
import { motion, AnimatePresence } from "motion/react"
|
||||
import { springs, motionTokens } from "@/lib/motion-tokens"
|
||||
|
||||
export function ExpandingCard({ title, body }: { title: string; body: string }) {
|
||||
const [expanded, setExpanded] = useState(false)
|
||||
|
||||
return (
|
||||
<motion.div layout onClick={() => setExpanded(!expanded)} className="cursor-pointer">
|
||||
{/* layout="position" prevents text reflow from animating */}
|
||||
<motion.h2 layout="position" className="font-semibold">
|
||||
{title}
|
||||
</motion.h2>
|
||||
|
||||
<AnimatePresence>
|
||||
{expanded && (
|
||||
<motion.p
|
||||
key="body"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
transition={{ duration: motionTokens.duration.fast }}
|
||||
>
|
||||
{body}
|
||||
</motion.p>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</motion.div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Shared-element crossfade
|
||||
|
||||
```tsx
|
||||
// Source context
|
||||
<motion.img layoutId="hero-image" src={src} className="w-16 h-16 rounded" />
|
||||
|
||||
// Destination context (same layoutId — motion handles the transition)
|
||||
<motion.img layoutId="hero-image" src={src} className="w-full rounded-xl" />
|
||||
```
|
||||
|
||||
### Accordion
|
||||
|
||||
```tsx
|
||||
<motion.div
|
||||
initial={false}
|
||||
animate={{ opacity: open ? 1 : 0, scaleY: open ? 1 : 0 }}
|
||||
style={{ transformOrigin: "top", overflow: "hidden" }}
|
||||
transition={{
|
||||
duration: motionTokens.duration.normal,
|
||||
ease: motionTokens.easing.smooth,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</motion.div>
|
||||
```
|
||||
|
||||
## End-to-End Example
|
||||
|
||||
A staggered list that enters on mount, handles conditional presence, and
|
||||
respects reduced motion — combining tokens, springs, AnimatePresence, and
|
||||
the accessibility hook from `motion-foundations`:
|
||||
|
||||
```tsx
|
||||
"use client"
|
||||
import { useState } from "react"
|
||||
import { motion, AnimatePresence } from "motion/react"
|
||||
import { motionTokens, springs } from "@/lib/motion-tokens"
|
||||
import { useSafeMotion } from "@/hooks/use-reduced-motion"
|
||||
|
||||
const containerVariants = {
|
||||
hidden: {},
|
||||
visible: {
|
||||
transition: { staggerChildren: 0.08, delayChildren: 0.1 },
|
||||
},
|
||||
}
|
||||
|
||||
function ListItem({ label, onRemove }: { label: string; onRemove: () => void }) {
|
||||
const safe = useSafeMotion(motionTokens.distance.sm)
|
||||
return (
|
||||
<motion.li
|
||||
variants={{
|
||||
hidden: safe.initial,
|
||||
visible: safe.animate,
|
||||
}}
|
||||
exit={safe.exit}
|
||||
transition={springs.gentle}
|
||||
className="flex items-center justify-between p-3 rounded-lg bg-white shadow-sm"
|
||||
>
|
||||
<span>{label}</span>
|
||||
<button onClick={onRemove}>Remove</button>
|
||||
</motion.li>
|
||||
)
|
||||
}
|
||||
|
||||
export function AnimatedList({ items, onRemove }: {
|
||||
items: { id: string; label: string }[]
|
||||
onRemove: (id: string) => void
|
||||
}) {
|
||||
return (
|
||||
<motion.ul
|
||||
variants={containerVariants}
|
||||
initial="hidden"
|
||||
animate="visible"
|
||||
className="space-y-2"
|
||||
>
|
||||
<AnimatePresence mode="popLayout">
|
||||
{items.map((item) => (
|
||||
<ListItem
|
||||
key={item.id}
|
||||
label={item.label}
|
||||
onRemove={() => onRemove(item.id)}
|
||||
/>
|
||||
))}
|
||||
</AnimatePresence>
|
||||
</motion.ul>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Constraints / Non-Goals
|
||||
|
||||
This skill does **not** cover:
|
||||
|
||||
- Token and spring definitions → see `motion-foundations`
|
||||
- Drag interactions, swipe gestures, reorderable lists → see `motion-advanced`
|
||||
- Text animations (word/character reveal, counters) → see `motion-advanced`
|
||||
- SVG path drawing or morphing → see `motion-advanced`
|
||||
- Custom animation hooks → see `motion-advanced`
|
||||
- CSS-only transitions not using `motion/react`
|
||||
|
||||
## Anti-Patterns
|
||||
|
||||
| Anti-pattern | Rule violated | Fix |
|
||||
| -------------------------------------------- | ------- | ------------------------------------------ |
|
||||
| `AnimatePresence` child missing `key` | Rule 1 | Add stable `key` to the direct child |
|
||||
| `initial` + `animate` without `exit` | Rule 2 | Always define all three together |
|
||||
| Page transition without `mode="wait"` | Rule 3 | Add `mode="wait"` to `AnimatePresence` |
|
||||
| `layout` on a 50-item list | Rule 4 | Use `mode="popLayout"` or explicit transforms |
|
||||
| `staggerChildren: 0.2` on a 10-item list | Rule 5 | Cap at `0.08–0.10` |
|
||||
| Modal without focus trap | Rule 6 | Add `focus-trap-react` or Radix Dialog |
|
||||
| `whileInView` without `viewport={{ once: true }}` | Rule 7 | Repeating entrances distract, not inform |
|
||||
| `transition={{ duration: 0.3 }}` inline | Rule 8 | Use `motionTokens.duration.normal` |
|
||||
|
||||
## Related Skills
|
||||
|
||||
- **`motion-foundations`** — defines all tokens, springs, the `useSafeMotion` hook, and SSR guards that every pattern here imports. Must be set up first.
|
||||
- **`motion-advanced`** — extends these patterns with drag, gestures, SVG, text, custom hooks, and imperative sequencing. Does not redefine any patterns from this skill.
|
||||
@@ -138,15 +138,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@isaacs/fs-minipass@npm:^4.0.0":
|
||||
version: 4.0.1
|
||||
resolution: "@isaacs/fs-minipass@npm:4.0.1"
|
||||
dependencies:
|
||||
minipass: "npm:^7.0.4"
|
||||
checksum: 10c0/c25b6dc1598790d5b55c0947a9b7d111cfa92594db5296c3b907e2f533c033666f692a3939eadac17b1c7c40d362d0b0635dc874cbfe3e70db7c2b07cc97a5d2
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@istanbuljs/schema@npm:^0.1.2, @istanbuljs/schema@npm:^0.1.3":
|
||||
version: 0.1.3
|
||||
resolution: "@istanbuljs/schema@npm:0.1.3"
|
||||
@@ -178,80 +169,30 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@msgpackr-extract/msgpackr-extract-darwin-arm64@npm:3.0.3":
|
||||
version: 3.0.3
|
||||
resolution: "@msgpackr-extract/msgpackr-extract-darwin-arm64@npm:3.0.3"
|
||||
conditions: os=darwin & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@msgpackr-extract/msgpackr-extract-darwin-x64@npm:3.0.3":
|
||||
version: 3.0.3
|
||||
resolution: "@msgpackr-extract/msgpackr-extract-darwin-x64@npm:3.0.3"
|
||||
conditions: os=darwin & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@msgpackr-extract/msgpackr-extract-linux-arm64@npm:3.0.3":
|
||||
version: 3.0.3
|
||||
resolution: "@msgpackr-extract/msgpackr-extract-linux-arm64@npm:3.0.3"
|
||||
conditions: os=linux & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@msgpackr-extract/msgpackr-extract-linux-arm@npm:3.0.3":
|
||||
version: 3.0.3
|
||||
resolution: "@msgpackr-extract/msgpackr-extract-linux-arm@npm:3.0.3"
|
||||
conditions: os=linux & cpu=arm
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@msgpackr-extract/msgpackr-extract-linux-x64@npm:3.0.3":
|
||||
version: 3.0.3
|
||||
resolution: "@msgpackr-extract/msgpackr-extract-linux-x64@npm:3.0.3"
|
||||
conditions: os=linux & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@msgpackr-extract/msgpackr-extract-win32-x64@npm:3.0.3":
|
||||
version: 3.0.3
|
||||
resolution: "@msgpackr-extract/msgpackr-extract-win32-x64@npm:3.0.3"
|
||||
conditions: os=win32 & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@opencode-ai/plugin@npm:^1.0.0":
|
||||
version: 1.14.33
|
||||
resolution: "@opencode-ai/plugin@npm:1.14.33"
|
||||
version: 1.3.15
|
||||
resolution: "@opencode-ai/plugin@npm:1.3.15"
|
||||
dependencies:
|
||||
"@opencode-ai/sdk": "npm:1.14.33"
|
||||
effect: "npm:4.0.0-beta.57"
|
||||
"@opencode-ai/sdk": "npm:1.3.15"
|
||||
zod: "npm:4.1.8"
|
||||
peerDependencies:
|
||||
"@opentui/core": ">=0.2.2"
|
||||
"@opentui/solid": ">=0.2.2"
|
||||
"@opentui/core": ">=0.1.96"
|
||||
"@opentui/solid": ">=0.1.96"
|
||||
peerDependenciesMeta:
|
||||
"@opentui/core":
|
||||
optional: true
|
||||
"@opentui/solid":
|
||||
optional: true
|
||||
checksum: 10c0/0ce3e9876e12e4d9afc664c1a03bc3bebb12147bdea9b640a1bc3ed3b871b284a75b294f72e8afc86af8140f0d89cb223367c12ee7af4d6c25e9c1373893d13a
|
||||
checksum: 10c0/1a662ff700812223310612f3c8c7fd4465eda5763d726ec4d29d0eae26babf344ef176c9b987d79fe1e29c8a498178881a47d7080bb9f4db3e70dad59eb8cd9e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@opencode-ai/sdk@npm:1.14.33":
|
||||
version: 1.14.33
|
||||
resolution: "@opencode-ai/sdk@npm:1.14.33"
|
||||
"@opencode-ai/sdk@npm:1.3.15":
|
||||
version: 1.3.15
|
||||
resolution: "@opencode-ai/sdk@npm:1.3.15"
|
||||
dependencies:
|
||||
cross-spawn: "npm:7.0.6"
|
||||
checksum: 10c0/10a52b224428fb05be055dbf2b96adbf1198a489a9eeedc68848231720559ec2cb98edf44b25afb2945a7609c485eea4265ea089d16fcdb4537113b78318ea21
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@standard-schema/spec@npm:^1.1.0":
|
||||
version: 1.1.0
|
||||
resolution: "@standard-schema/spec@npm:1.1.0"
|
||||
checksum: 10c0/d90f55acde4b2deb983529c87e8025fa693de1a5e8b49ecc6eb84d1fd96328add0e03d7d551442156c7432fd78165b2c26ff561b970a9a881f046abb78d6a526
|
||||
checksum: 10c0/3957ae62e0ec1e339d9493e03a2440c95afdd64a608a2dc9db8383338650318a294280b2142305db5b0147badacbefa0d07e949d31167e5a4a49c9d057d016fa
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -315,13 +256,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"abbrev@npm:^4.0.0":
|
||||
version: 4.0.0
|
||||
resolution: "abbrev@npm:4.0.0"
|
||||
checksum: 10c0/b4cc16935235e80702fc90192e349e32f8ef0ed151ef506aa78c81a7c455ec18375c4125414b99f84b2e055199d66383e787675f0bcd87da7a4dbd59f9eac1d5
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"acorn-jsx@npm:^5.3.2":
|
||||
version: 5.3.2
|
||||
resolution: "acorn-jsx@npm:5.3.2"
|
||||
@@ -341,26 +275,26 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"ajv@npm:^6.12.4":
|
||||
version: 6.15.0
|
||||
resolution: "ajv@npm:6.15.0"
|
||||
version: 6.14.0
|
||||
resolution: "ajv@npm:6.14.0"
|
||||
dependencies:
|
||||
fast-deep-equal: "npm:^3.1.1"
|
||||
fast-json-stable-stringify: "npm:^2.0.0"
|
||||
json-schema-traverse: "npm:^0.4.1"
|
||||
uri-js: "npm:^4.2.2"
|
||||
checksum: 10c0/67966499dd272ecde1c2e467084411132891523d057487587879d39ac04207f4351b7b2324c83198013967fbfa632c1612adc960114a30770fbe07a0773b32c2
|
||||
checksum: 10c0/a2bc39b0555dc9802c899f86990eb8eed6e366cddbf65be43d5aa7e4f3c4e1a199d5460fd7ca4fb3d864000dbbc049253b72faa83b3b30e641ca52cb29a68c22
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ajv@npm:^8.18.0":
|
||||
version: 8.20.0
|
||||
resolution: "ajv@npm:8.20.0"
|
||||
version: 8.18.0
|
||||
resolution: "ajv@npm:8.18.0"
|
||||
dependencies:
|
||||
fast-deep-equal: "npm:^3.1.3"
|
||||
fast-uri: "npm:^3.0.1"
|
||||
json-schema-traverse: "npm:^1.0.0"
|
||||
require-from-string: "npm:^2.0.2"
|
||||
checksum: 10c0/5df9a1c8f83863cde1bd3a9ddb426f599718f88e3dc9153616c79fb28e0be455335830d7f21d745576519f057b371352daa31047b6a33d7036fe08777d60cf2a
|
||||
checksum: 10c0/e7517c426173513a07391be951879932bdf3348feaebd2199f5b901c20f99d60db8cd1591502d4d551dc82f594e82a05c4fe1c70139b15b8937f7afeaed9532f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -491,13 +425,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"chownr@npm:^3.0.0":
|
||||
version: 3.0.0
|
||||
resolution: "chownr@npm:3.0.0"
|
||||
checksum: 10c0/43925b87700f7e3893296c8e9c56cc58f926411cce3a6e5898136daaf08f08b9a8eb76d37d3267e707d0dcc17aed2e2ebdf5848c0c3ce95cf910a919935c1b10
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"cliui@npm:^8.0.1":
|
||||
version: 8.0.1
|
||||
resolution: "cliui@npm:8.0.1"
|
||||
@@ -606,13 +533,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"detect-libc@npm:^2.0.1":
|
||||
version: 2.1.2
|
||||
resolution: "detect-libc@npm:2.1.2"
|
||||
checksum: 10c0/acc675c29a5649fa1fb6e255f993b8ee829e510b6b56b0910666949c80c364738833417d0edb5f90e4e46be17228b0f2b66a010513984e18b15deeeac49369c4
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"devlop@npm:^1.0.0":
|
||||
version: 1.1.0
|
||||
resolution: "devlop@npm:1.1.0"
|
||||
@@ -643,24 +563,6 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"effect@npm:4.0.0-beta.57":
|
||||
version: 4.0.0-beta.57
|
||||
resolution: "effect@npm:4.0.0-beta.57"
|
||||
dependencies:
|
||||
"@standard-schema/spec": "npm:^1.1.0"
|
||||
fast-check: "npm:^4.6.0"
|
||||
find-my-way-ts: "npm:^0.1.6"
|
||||
ini: "npm:^6.0.0"
|
||||
kubernetes-types: "npm:^1.30.0"
|
||||
msgpackr: "npm:^1.11.9"
|
||||
multipasta: "npm:^0.2.7"
|
||||
toml: "npm:^4.1.1"
|
||||
uuid: "npm:^13.0.0"
|
||||
yaml: "npm:^2.8.3"
|
||||
checksum: 10c0/0ae765176b305f6ec9c067122cdd0adae8c83b233973df57200b3fb68e417f94cd7e539e71fff520f9c98be59404a23d68989cd43a4b53d9926e9ae91ee13a44
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"emoji-regex@npm:^8.0.0":
|
||||
version: 8.0.0
|
||||
resolution: "emoji-regex@npm:8.0.0"
|
||||
@@ -675,13 +577,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"env-paths@npm:^2.2.0":
|
||||
version: 2.2.1
|
||||
resolution: "env-paths@npm:2.2.1"
|
||||
checksum: 10c0/285325677bf00e30845e330eec32894f5105529db97496ee3f598478e50f008c5352a41a30e5e72ec9de8a542b5a570b85699cd63bd2bc646dbcb9f311d83bc4
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"escalade@npm:^3.1.1":
|
||||
version: 3.2.0
|
||||
resolution: "escalade@npm:3.2.0"
|
||||
@@ -812,22 +707,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"exponential-backoff@npm:^3.1.1":
|
||||
version: 3.1.3
|
||||
resolution: "exponential-backoff@npm:3.1.3"
|
||||
checksum: 10c0/77e3ae682b7b1f4972f563c6dbcd2b0d54ac679e62d5d32f3e5085feba20483cf28bd505543f520e287a56d4d55a28d7874299941faf637e779a1aa5994d1267
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"fast-check@npm:^4.6.0":
|
||||
version: 4.7.0
|
||||
resolution: "fast-check@npm:4.7.0"
|
||||
dependencies:
|
||||
pure-rand: "npm:^8.0.0"
|
||||
checksum: 10c0/7edce2b82d11d5325e9e79a2377e1f6e7200d27219edda2e3449d827e994c34461132fc149c90e41b78fc8e6ef4aae77d45350ac7bb1bc4a81110401d0a49fbc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"fast-deep-equal@npm:^3.1.1, fast-deep-equal@npm:^3.1.3":
|
||||
version: 3.1.3
|
||||
resolution: "fast-deep-equal@npm:3.1.3"
|
||||
@@ -877,13 +756,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"find-my-way-ts@npm:^0.1.6":
|
||||
version: 0.1.6
|
||||
resolution: "find-my-way-ts@npm:0.1.6"
|
||||
checksum: 10c0/16ad4b15275b56ee0ec361d0c61afbdff4c75bd0ac04112f6910f188cb1058096ba63529c2363914da6bb60266aa4def1025af04af26368ff87eb0df52f2862f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"find-up@npm:^5.0.0":
|
||||
version: 5.0.0
|
||||
resolution: "find-up@npm:5.0.0"
|
||||
@@ -963,16 +835,9 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"globals@npm:^17.4.0":
|
||||
version: 17.6.0
|
||||
resolution: "globals@npm:17.6.0"
|
||||
checksum: 10c0/cf94fb4329cc5c68cf81018fd68324f413181ee169f0235b0b33b82bc93fe7825a21beea951f83a80e8e4bbdad9c0c80515a145b5fd4b5cb52f2a80db899a93f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"graceful-fs@npm:^4.2.6":
|
||||
version: 4.2.11
|
||||
resolution: "graceful-fs@npm:4.2.11"
|
||||
checksum: 10c0/386d011a553e02bc594ac2ca0bd6d9e4c22d7fa8cfbfc448a6d148c59ea881b092db9dbe3547ae4b88e55f1b01f7c4a2ecc53b310c042793e63aa44cf6c257f2
|
||||
version: 17.4.0
|
||||
resolution: "globals@npm:17.4.0"
|
||||
checksum: 10c0/2be9e8c2b9035836f13d420b22f0247a328db82967d3bebfc01126d888ed609305f06c05895914e969653af5c6ba35fd7a0920f3e6c869afa60666c810630feb
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -1021,13 +886,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ini@npm:^6.0.0":
|
||||
version: 6.0.0
|
||||
resolution: "ini@npm:6.0.0"
|
||||
checksum: 10c0/9a7f55f306e2b25b41ae67c8b526e8f4673f057b70852b9025816ef4f15f07bf1ba35ed68ea4471ff7b31718f7ef1bc50d709f8d03cb012e10a3135eb99c7206
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ini@npm:~4.1.0":
|
||||
version: 4.1.3
|
||||
resolution: "ini@npm:4.1.3"
|
||||
@@ -1096,13 +954,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"isexe@npm:^4.0.0":
|
||||
version: 4.0.0
|
||||
resolution: "isexe@npm:4.0.0"
|
||||
checksum: 10c0/5884815115bceac452877659a9c7726382531592f43dc29e5d48b7c4100661aed54018cb90bd36cb2eaeba521092570769167acbb95c18d39afdccbcca06c5ce
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"istanbul-lib-coverage@npm:^3.0.0, istanbul-lib-coverage@npm:^3.2.0":
|
||||
version: 3.2.2
|
||||
resolution: "istanbul-lib-coverage@npm:3.2.2"
|
||||
@@ -1204,13 +1055,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"kubernetes-types@npm:^1.30.0":
|
||||
version: 1.30.0
|
||||
resolution: "kubernetes-types@npm:1.30.0"
|
||||
checksum: 10c0/de3641e4f50cfc123c4102a73c12932e1db8e51783c7cae4ea8ad3561bd56fab0f1c2346801f84a4c36aae8cea0b25d21e9514cc0fcecd4d64b1314043263076
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"levn@npm:^0.4.1":
|
||||
version: 0.4.1
|
||||
resolution: "levn@npm:0.4.1"
|
||||
@@ -1644,22 +1488,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"minipass@npm:^7.0.4, minipass@npm:^7.1.2, minipass@npm:^7.1.3":
|
||||
"minipass@npm:^7.1.2, minipass@npm:^7.1.3":
|
||||
version: 7.1.3
|
||||
resolution: "minipass@npm:7.1.3"
|
||||
checksum: 10c0/539da88daca16533211ea5a9ee98dc62ff5742f531f54640dd34429e621955e91cc280a91a776026264b7f9f6735947629f920944e9c1558369e8bf22eb33fbb
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"minizlib@npm:^3.1.0":
|
||||
version: 3.1.0
|
||||
resolution: "minizlib@npm:3.1.0"
|
||||
dependencies:
|
||||
minipass: "npm:^7.1.2"
|
||||
checksum: 10c0/5aad75ab0090b8266069c9aabe582c021ae53eb33c6c691054a13a45db3b4f91a7fb1bd79151e6b4e9e9a86727b522527c0a06ec7d45206b745d54cd3097bcec
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ms@npm:^2.1.3":
|
||||
version: 2.1.3
|
||||
resolution: "ms@npm:2.1.3"
|
||||
@@ -1667,56 +1502,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"msgpackr-extract@npm:^3.0.2":
|
||||
version: 3.0.3
|
||||
resolution: "msgpackr-extract@npm:3.0.3"
|
||||
dependencies:
|
||||
"@msgpackr-extract/msgpackr-extract-darwin-arm64": "npm:3.0.3"
|
||||
"@msgpackr-extract/msgpackr-extract-darwin-x64": "npm:3.0.3"
|
||||
"@msgpackr-extract/msgpackr-extract-linux-arm": "npm:3.0.3"
|
||||
"@msgpackr-extract/msgpackr-extract-linux-arm64": "npm:3.0.3"
|
||||
"@msgpackr-extract/msgpackr-extract-linux-x64": "npm:3.0.3"
|
||||
"@msgpackr-extract/msgpackr-extract-win32-x64": "npm:3.0.3"
|
||||
node-gyp: "npm:latest"
|
||||
node-gyp-build-optional-packages: "npm:5.2.2"
|
||||
dependenciesMeta:
|
||||
"@msgpackr-extract/msgpackr-extract-darwin-arm64":
|
||||
optional: true
|
||||
"@msgpackr-extract/msgpackr-extract-darwin-x64":
|
||||
optional: true
|
||||
"@msgpackr-extract/msgpackr-extract-linux-arm":
|
||||
optional: true
|
||||
"@msgpackr-extract/msgpackr-extract-linux-arm64":
|
||||
optional: true
|
||||
"@msgpackr-extract/msgpackr-extract-linux-x64":
|
||||
optional: true
|
||||
"@msgpackr-extract/msgpackr-extract-win32-x64":
|
||||
optional: true
|
||||
bin:
|
||||
download-msgpackr-prebuilds: bin/download-prebuilds.js
|
||||
checksum: 10c0/e504fd8bf86a29d7527c83776530ee6dc92dcb0273bb3679fd4a85173efead7f0ee32fb82c8410a13c33ef32828c45f81118ffc0fbed5d6842e72299894623b4
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"msgpackr@npm:^1.11.9":
|
||||
version: 1.11.12
|
||||
resolution: "msgpackr@npm:1.11.12"
|
||||
dependencies:
|
||||
msgpackr-extract: "npm:^3.0.2"
|
||||
dependenciesMeta:
|
||||
msgpackr-extract:
|
||||
optional: true
|
||||
checksum: 10c0/e9f1460e363dbd8c81a5c1b5829980edea7d76e91d570d094d0a4dae0d8ad12f64dea11b2be15f3d7b48d615fa9d3c9b600a6894fd272526087fa33753b5fd16
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"multipasta@npm:^0.2.7":
|
||||
version: 0.2.7
|
||||
resolution: "multipasta@npm:0.2.7"
|
||||
checksum: 10c0/15917ac88aeefa5b8afac44b90d1e9d0d0ec7148b51e0766f07a69a220ecebcb6404539a856c45aa85a3d7fe517bc58febe81437146705f17ecd2961dc0b9fa5
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"natural-compare@npm:^1.4.0":
|
||||
version: 1.4.0
|
||||
resolution: "natural-compare@npm:1.4.0"
|
||||
@@ -1724,50 +1509,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"node-gyp-build-optional-packages@npm:5.2.2":
|
||||
version: 5.2.2
|
||||
resolution: "node-gyp-build-optional-packages@npm:5.2.2"
|
||||
dependencies:
|
||||
detect-libc: "npm:^2.0.1"
|
||||
bin:
|
||||
node-gyp-build-optional-packages: bin.js
|
||||
node-gyp-build-optional-packages-optional: optional.js
|
||||
node-gyp-build-optional-packages-test: build-test.js
|
||||
checksum: 10c0/c81128c6f91873381be178c5eddcbdf66a148a6a89a427ce2bcd457593ce69baf2a8662b6d22cac092d24aa9c43c230dec4e69b3a0da604503f4777cd77e282b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"node-gyp@npm:latest":
|
||||
version: 12.3.0
|
||||
resolution: "node-gyp@npm:12.3.0"
|
||||
dependencies:
|
||||
env-paths: "npm:^2.2.0"
|
||||
exponential-backoff: "npm:^3.1.1"
|
||||
graceful-fs: "npm:^4.2.6"
|
||||
nopt: "npm:^9.0.0"
|
||||
proc-log: "npm:^6.0.0"
|
||||
semver: "npm:^7.3.5"
|
||||
tar: "npm:^7.5.4"
|
||||
tinyglobby: "npm:^0.2.12"
|
||||
undici: "npm:^6.25.0"
|
||||
which: "npm:^6.0.0"
|
||||
bin:
|
||||
node-gyp: bin/node-gyp.js
|
||||
checksum: 10c0/9d9032b405cbe42f72a105259d9eb679376470c102df4a2dbaa51e07d59bf741dcffb85897087ea9d8318b9cabb824a8978af51508ae142f0239ae1e6a3c2329
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"nopt@npm:^9.0.0":
|
||||
version: 9.0.0
|
||||
resolution: "nopt@npm:9.0.0"
|
||||
dependencies:
|
||||
abbrev: "npm:^4.0.0"
|
||||
bin:
|
||||
nopt: bin/nopt.js
|
||||
checksum: 10c0/1822eb6f9b020ef6f7a7516d7b64a8036e09666ea55ac40416c36e4b2b343122c3cff0e2f085675f53de1d2db99a2a89a60ccea1d120bcd6a5347bf6ceb4a7fd
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"optionator@npm:^0.9.3":
|
||||
version: 0.9.4
|
||||
resolution: "optionator@npm:0.9.4"
|
||||
@@ -1848,7 +1589,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"picomatch@npm:^4.0.3, picomatch@npm:^4.0.4":
|
||||
"picomatch@npm:^4.0.3":
|
||||
version: 4.0.4
|
||||
resolution: "picomatch@npm:4.0.4"
|
||||
checksum: 10c0/e2c6023372cc7b5764719a5ffb9da0f8e781212fa7ca4bd0562db929df8e117460f00dff3cb7509dacfc06b86de924b247f504d0ce1806a37fac4633081466b0
|
||||
@@ -1862,13 +1603,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"proc-log@npm:^6.0.0":
|
||||
version: 6.1.0
|
||||
resolution: "proc-log@npm:6.1.0"
|
||||
checksum: 10c0/4f178d4062733ead9d71a9b1ab24ebcecdfe2250916a5b1555f04fe2eda972a0ec76fbaa8df1ad9c02707add6749219d118a4fc46dc56bdfe4dde4b47d80bb82
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"punycode.js@npm:^2.3.1":
|
||||
version: 2.3.1
|
||||
resolution: "punycode.js@npm:2.3.1"
|
||||
@@ -1883,13 +1617,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"pure-rand@npm:^8.0.0":
|
||||
version: 8.4.0
|
||||
resolution: "pure-rand@npm:8.4.0"
|
||||
checksum: 10c0/6414bbc1c6f45fb774173431c7205e79783b77cfae0e2145e741b6999363554dbd2f4210d2a5bc08683e0b2f6823198c9308766b1d0911e1dccd7beb8842f860
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"require-directory@npm:^2.1.1":
|
||||
version: 2.1.1
|
||||
resolution: "require-directory@npm:2.1.1"
|
||||
@@ -1925,7 +1652,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"semver@npm:^7.3.5, semver@npm:^7.5.3":
|
||||
"semver@npm:^7.5.3":
|
||||
version: 7.7.4
|
||||
resolution: "semver@npm:7.7.4"
|
||||
bin:
|
||||
@@ -2026,19 +1753,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tar@npm:^7.5.4":
|
||||
version: 7.5.13
|
||||
resolution: "tar@npm:7.5.13"
|
||||
dependencies:
|
||||
"@isaacs/fs-minipass": "npm:^4.0.0"
|
||||
chownr: "npm:^3.0.0"
|
||||
minipass: "npm:^7.1.2"
|
||||
minizlib: "npm:^3.1.0"
|
||||
yallist: "npm:^5.0.0"
|
||||
checksum: 10c0/5c65b8084799bde7a791593a1c1a45d3d6ee98182e3700b24c247b7b8f8654df4191642abbdb07ff25043d45dcff35620827c3997b88ae6c12040f64bed5076b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"test-exclude@npm:^8.0.0":
|
||||
version: 8.0.0
|
||||
resolution: "test-exclude@npm:8.0.0"
|
||||
@@ -2050,16 +1764,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tinyglobby@npm:^0.2.12":
|
||||
version: 0.2.16
|
||||
resolution: "tinyglobby@npm:0.2.16"
|
||||
dependencies:
|
||||
fdir: "npm:^6.5.0"
|
||||
picomatch: "npm:^4.0.4"
|
||||
checksum: 10c0/f2e09fd93dd95c41e522113b686ff6f7c13020962f8698a864a257f3d7737599afc47722b7ab726e12f8a813f779906187911ff8ee6701ede65072671a7e934b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tinyglobby@npm:~0.2.15":
|
||||
version: 0.2.15
|
||||
resolution: "tinyglobby@npm:0.2.15"
|
||||
@@ -2070,13 +1774,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"toml@npm:^4.1.1":
|
||||
version: 4.1.1
|
||||
resolution: "toml@npm:4.1.1"
|
||||
checksum: 10c0/077bc02ac1ce82091ea073f675d7e2a1df487d1b18bbc7e653daba4956d545954b7095e979b8792f0837339b901ee190ad4464342e5e377c36bbdeca8903e079
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"type-check@npm:^0.4.0, type-check@npm:~0.4.0":
|
||||
version: 0.4.0
|
||||
resolution: "type-check@npm:0.4.0"
|
||||
@@ -2120,13 +1817,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"undici@npm:^6.25.0":
|
||||
version: 6.25.0
|
||||
resolution: "undici@npm:6.25.0"
|
||||
checksum: 10c0/2597cc6689bdb02c210c557b1f85febbfda65becae6e6fc1061508e2f33734d25207f81cd8af56ada9956329eb3a7bd7431e87dcfeceba20ee87059b57dcf985
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"uri-js@npm:^4.2.2":
|
||||
version: 4.4.1
|
||||
resolution: "uri-js@npm:4.4.1"
|
||||
@@ -2136,15 +1826,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"uuid@npm:^13.0.0":
|
||||
version: 13.0.1
|
||||
resolution: "uuid@npm:13.0.1"
|
||||
bin:
|
||||
uuid: dist-node/bin/uuid
|
||||
checksum: 10c0/7bb8ad18b11871b7bd1b9161a60610c2b6ce8f7300d93932f92117a2ab9b40479dd23e81929ac848e8a7c45f78b8ed3333f88694b71c17ff3265e443f8684642
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"v8-to-istanbul@npm:^9.0.0":
|
||||
version: 9.3.0
|
||||
resolution: "v8-to-istanbul@npm:9.3.0"
|
||||
@@ -2167,17 +1848,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"which@npm:^6.0.0":
|
||||
version: 6.0.1
|
||||
resolution: "which@npm:6.0.1"
|
||||
dependencies:
|
||||
isexe: "npm:^4.0.0"
|
||||
bin:
|
||||
node-which: bin/which.js
|
||||
checksum: 10c0/7e710e54ea36d2d6183bee2f9caa27a3b47b9baf8dee55a199b736fcf85eab3b9df7556fca3d02b50af7f3dfba5ea3a45644189836df06267df457e354da66d5
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"word-wrap@npm:^1.2.5":
|
||||
version: 1.2.5
|
||||
resolution: "word-wrap@npm:1.2.5"
|
||||
@@ -2203,22 +1873,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"yallist@npm:^5.0.0":
|
||||
version: 5.0.0
|
||||
resolution: "yallist@npm:5.0.0"
|
||||
checksum: 10c0/a499c81ce6d4a1d260d4ea0f6d49ab4da09681e32c3f0472dee16667ed69d01dae63a3b81745a24bd78476ec4fcf856114cb4896ace738e01da34b2c42235416
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"yaml@npm:^2.8.3":
|
||||
version: 2.8.4
|
||||
resolution: "yaml@npm:2.8.4"
|
||||
bin:
|
||||
yaml: bin.mjs
|
||||
checksum: 10c0/0a33a1fa28d4bc79f61a12ec7ef7a2bce0ce5f8e80c6eaecfb4a0c88c08767dd1ede372b6a3bcd70891213b8c9f3169b355c97e77026d3b3459e10d2cccaef1e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"yargs-parser@npm:^21.1.1":
|
||||
version: 21.1.1
|
||||
resolution: "yargs-parser@npm:21.1.1"
|
||||
|
||||
Reference in New Issue
Block a user