A portable AI agent skill for building polished, copy-pasteable animated React components in the orea style — dark theme, Framer Motion physics, and a consistent design language.
Works with any AGENTS.md/SKILL.md-aware agent: Claude Code · Antigravity · Codex · OpenCode · Cursor · Windsurf.
Drop this skill into your agent config and it will know how to:
- Build self-contained
"use client"animated React components sized for a preview tile - Apply the orea design language (dark neutral palette, hairline borders, single blue accent)
- Use the right Framer Motion recipe for each interaction type (tilt, magnetic, count-up, marquee, toast stacks…)
- Register components in
registry.tsxwith a faithful copy-pastecodestring - Respect accessibility: semantic HTML,
aria-*, keyboard support, andprefers-reduced-motion
orea-skill/
├── SKILL.md # Skill entry point — frontmatter + full instructions
├── AGENTS.md # Project-level agent context (orea main repo)
├── examples/
│ └── component-template.tsx # Starting scaffold for every new component
└── references/
├── design-tokens.md # Colors, radius, typography, spacing, preview sizing
├── animation-patterns.md # Copy-ready Framer Motion recipes
└── registry-workflow.md # How to add a component to registry.tsx
# inside your project root
git clone https://github.com/oreaui/orea-skill .agents/skills/orea-animated-uiThe agent will auto-discover any skill placed under .agents/skills/.
git clone https://github.com/oreaui/orea-skill \
"$HOME/.gemini/config/skills/orea-animated-ui"Replace $HOME/.gemini/config with the global customizations root for your agent of choice.
If you keep skills in a non-standard location, register the path in your skills.json:
{
"entries": [
{ "path": "path/to/orea-animated-ui" }
]
}Once installed, prompt your agent with any of the following and the skill will activate automatically:
| Prompt example | What happens |
|---|---|
"Add an animated card component" |
Scaffolds a new file + registry entry |
"Make a magnetic button effect" |
Uses the spring-follow recipe from animation-patterns.md |
"Build a count-up stat with in-view trigger" |
Uses useInView + animate() pattern |
"Add a marquee / ticker" |
Creates a span: 3 wide component |
"Make it feel more alive" |
Adds tasteful micro-interactions to an existing component |
Every component the skill produces follows these invariants:
| Rule | Requirement |
|---|---|
"use client" |
First line, always |
| Export | Single named export, PascalCase, zero required props |
| Preview size | Fits inside a h-64 tile (canonical card: h-44 w-72) |
| Motion engine | Framer Motion only — useSpring, useMotionValue, AnimatePresence |
| Palette | neutral-900/950 surfaces, white/10 borders, one blue-500 accent |
| Icons | lucide-react only, h-4 w-4 or h-5 w-5 |
| Accessibility | Semantic elements, aria-*, keyboard support, prefers-reduced-motion |
| Registry | File and matching registry.tsx entry created together |
| Interaction | Pattern |
|---|---|
| 3D pointer tilt + glare | useMotionValue + useSpring + useMotionTemplate |
| Magnetic / follow-cursor | Spring-wrapped x/y motion values |
| Count-up / progress | animate() + useTransform + useInView |
| Enter/exit, toasts, stacks | AnimatePresence + mode="popLayout" + layout |
| Marquee, shimmer, aurora | animate prop with repeat: Infinity |
Spring presets:
- Snappy (interactions):
{ stiffness: 220–300, damping: 15–20 } - Soft settle (reveals): lower stiffness, higher damping
| Role | Tailwind classes |
|---|---|
| Deep surface | bg-neutral-950 |
| Card surface | bg-neutral-900 / bg-gradient-to-br from-neutral-800 to-neutral-900 |
| Border | border border-white/10 |
| Primary text | text-neutral-50 |
| Secondary text | text-neutral-400 |
| Accent | text-blue-400 / bg-blue-500 |
Full token reference → references/design-tokens.md
"use client"
import { useRef } from "react"
import {
motion,
useMotionValue,
useSpring,
useMotionTemplate,
useReducedMotion,
} from "framer-motion"
export function MyComponent() {
const reduce = useReducedMotion()
const ref = useRef<HTMLDivElement>(null)
const rotateX = useSpring(useMotionValue(0), { stiffness: 220, damping: 18 })
const rotateY = useSpring(useMotionValue(0), { stiffness: 220, damping: 18 })
const glareX = useMotionValue(50)
const glareY = useMotionValue(50)
const glare = useMotionTemplate`radial-gradient(circle at ${glareX}% ${glareY}%, rgba(255,255,255,0.28), transparent 55%)`
function handleMove(e: React.MouseEvent) {
if (reduce) return
const rect = ref.current?.getBoundingClientRect()
if (!rect) return
const px = (e.clientX - rect.left) / rect.width
const py = (e.clientY - rect.top) / rect.height
rotateY.set((px - 0.5) * 20)
rotateX.set((0.5 - py) * 20)
glareX.set(px * 100)
glareY.set(py * 100)
}
return (
<div style={{ perspective: 900 }}>
<motion.div
ref={ref}
onMouseMove={handleMove}
onMouseLeave={() => { rotateX.set(0); rotateY.set(0) }}
style={{ rotateX, rotateY, transformStyle: "preserve-3d" }}
className="relative h-44 w-72 overflow-hidden rounded-2xl border border-white/10 bg-gradient-to-br from-neutral-800 to-neutral-900 p-5"
>
<motion.div
className="pointer-events-none absolute inset-0"
style={{ background: glare }}
/>
<div className="relative flex h-full flex-col justify-between">
<span className="text-xs font-medium text-neutral-400">Label</span>
<div>
<p className="text-lg font-semibold tracking-tight text-neutral-50">
Your component
</p>
<p className="text-sm leading-relaxed text-neutral-400">
Describe the effect.
</p>
</div>
</div>
</motion.div>
</div>
)
}Full scaffold → examples/component-template.tsx
| File | Purpose |
|---|---|
SKILL.md |
Full skill instructions loaded by the agent |
references/design-tokens.md |
Colors, radius, typography, spacing, preview sizing |
references/animation-patterns.md |
Copy-ready Framer Motion recipes |
references/registry-workflow.md |
Steps to register a component + code escaping rules |
examples/component-template.tsx |
Starting scaffold for every new component |
MIT — see SKILL.md frontmatter.