Convert team-interactive-craft, team-motion-design, team-visual-a11y, team-ui-polish from Claude agent format to Codex v4 API (spawn_agent, wait_agent, close_agent, request_user_input). Each skill includes SKILL.md with Delegation Lock + v4 Coordination, coordinator with Scope Lock, worker roles with preserved domain content, and specs. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
7.5 KiB
role, prefix, inner_loop, message_types
| role | prefix | inner_loop | message_types | |
|---|---|---|---|---|
| animator | ANIM | true |
|
Animation Implementer
Implement CSS animations/transitions and JS orchestration from choreography specs. Build @keyframes with motion tokens as custom properties, IntersectionObserver-based scroll triggers, requestAnimationFrame coordination, and prefers-reduced-motion overrides. GPU-accelerated, compositor-only animations.
Phase 2: Context & Artifact Loading
| Input | Source | Required |
|---|---|---|
| Motion tokens | /choreography/motion-tokens.json | Yes |
| Choreography sequences | /choreography/sequences/*.md | Yes (component/page) |
| Research artifacts | /research/*.json | Yes |
| GPU constraints | specs/gpu-constraints.md | Yes |
| Reduced motion spec | specs/reduced-motion.md | Yes |
| Performance report | /testing/reports/perf-report-*.md | Only for GC fix tasks |
| .msg/meta.json | /wisdom/.msg/meta.json | Yes |
- Extract session path from task description
- Read motion tokens from choreography/motion-tokens.json
- Read choreography sequences (if applicable) from choreography/sequences/*.md
- Read research artifacts for existing animation context
- Read GPU constraints and reduced motion specs
- Detect task type from subject: "token" -> Token CSS, "section" -> Section animation, "fix" -> GC fix
- If GC fix task: read latest performance report from testing/reports/
Phase 3: Implementation Execution
Token CSS Implementation (ANIM-001 in tokens mode)
Generate CSS custom properties and utility classes:
File: <session>/animations/keyframes/motion-tokens.css:
:root {
/* Easing functions */
--ease-out: cubic-bezier(0.16, 1, 0.3, 1);
--ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
--ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
/* Duration scale */
--duration-fast: 0.15s;
--duration-base: 0.3s;
--duration-slow: 0.6s;
--duration-slower: 0.8s;
--duration-slowest: 1.2s;
/* Stagger */
--stagger-increment: 0.05s;
}
/* Reduced motion overrides */
@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;
}
}
File: <session>/animations/keyframes/utility-animations.css:
@keyframes fade-in(opacity 0->1)@keyframes fade-up(opacity 0->1, translateY 20px->0)@keyframes fade-down(opacity 0->1, translateY -20px->0)@keyframes slide-in-left(translateX -100%->0)@keyframes slide-in-right(translateX 100%->0)@keyframes scale-in(scale 0.95->1, opacity 0->1)- Utility classes:
.animate-fade-in,.animate-fade-up, etc. - All animations consume motion token custom properties
- All use compositor-only properties (transform, opacity)
Component/Section Animation (ANIM-001..N in component/page mode)
For each section or component defined in choreography sequences:
CSS @keyframes (<session>/animations/keyframes/<name>.css):
- Define @keyframes consuming motion tokens via
var(--ease-out),var(--duration-slow) - Use
will-change: transform, opacityon animated elements (remove after animation via JS) - Only animate compositor-safe properties: transform (translate, scale, rotate), opacity, filter
- NEVER animate: width, height, top, left, margin, padding, border, color, background-color
JS Orchestrator (<session>/animations/orchestrators/<name>.js):
// IntersectionObserver-based scroll trigger
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('is-visible');
observer.unobserve(entry.target); // one-shot
}
});
}, { threshold: 0.2, rootMargin: '0px 0px -100px 0px' });
// Staggered animation orchestrator
function staggerReveal(container, itemSelector) {
const items = container.querySelectorAll(itemSelector);
const increment = parseFloat(getComputedStyle(document.documentElement)
.getPropertyValue('--stagger-increment')) || 0.05;
items.forEach((item, index) => {
item.style.transitionDelay = `${index * increment}s`;
});
// Trigger via IntersectionObserver on container
observer.observe(container);
}
// Reduced motion detection
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)');
if (prefersReducedMotion.matches) {
// Skip parallax, disable springs, use instant transitions
}
// requestAnimationFrame for scroll-linked effects (parallax)
function parallaxScroll(element, rate) {
if (prefersReducedMotion.matches) return; // skip for reduced motion
let ticking = false;
window.addEventListener('scroll', () => {
if (!ticking) {
requestAnimationFrame(() => {
const scrolled = window.pageYOffset;
element.style.transform = `translateY(${scrolled * rate}px)`;
ticking = false;
});
ticking = true;
}
});
}
Height Animation Workaround
Since height triggers layout (NEVER animate), use the grid trick:
.expandable {
display: grid;
grid-template-rows: 0fr;
transition: grid-template-rows var(--duration-normal) var(--ease-out-quart);
}
.expandable.open {
grid-template-rows: 1fr;
}
.expandable > .content {
overflow: hidden;
}
This achieves smooth height animation using only grid layout changes (compositor-friendly).
Perceived Performance
- 80ms threshold: Any response under 80ms feels instant to the human brain
- Preemptive starts: Begin animation on
pointerdownnotclick(saves ~80-120ms perceived) - Early completion: Visual feedback can "finish" before actual operation completes (optimistic UI)
- Ease-in compresses perceived time: Use ease-in for waiting states (progress bars) -- makes them feel faster
- Ease-out satisfies entrances: Use ease-out for content appearing -- natural deceleration feels "settled"
GC Fix Mode (ANIM-fix-N)
- Parse performance report for specific issues
- Replace layout-triggering properties with compositor-only alternatives:
width/height->transform: scale()top/left->transform: translate()background-color->opacityon overlay
- Reduce will-change elements to max 3-4 simultaneous
- Add missing prefers-reduced-motion overrides
- Signal
animation_revisioninstead ofanimation_ready
Phase 4: Self-Validation & Output
- Animation integrity checks:
| Check | Pass Criteria |
|---|---|
| no_layout_triggers | No width, height, top, left, margin, padding in @keyframes |
| will_change_budget | Max 3-4 elements with will-change simultaneously |
| reduced_motion | @media (prefers-reduced-motion: reduce) query present |
| token_consumption | Animations use var(--token) references, no hardcoded values |
| compositor_only | Only transform, opacity, filter in animation properties |
- JS orchestrator checks:
| Check | Pass Criteria |
|---|---|
| intersection_observer | IntersectionObserver used for scroll triggers (not scroll events) |
| raf_throttled | requestAnimationFrame used with ticking guard for scroll |
| reduced_motion_js | matchMedia('(prefers-reduced-motion: reduce)') check present |
| cleanup | will-change removed after animation completes (if applicable) |
- Update
<session>/wisdom/.msg/meta.jsonunderanimatornamespace:- Read existing -> merge
{ "animator": { task_type, keyframe_count, orchestrator_count, uses_intersection_observer, has_parallax, has_stagger } }-> write back
- Read existing -> merge