attention-choreography
Skill7 starsChanged 10 months agoMITView on GitHub
Reading order and gaze path choreography for animation. Use when sequencing element entrances by visual importance - F-pattern for text content, Z-pattern for landing pages, center-out for hero sections, or custom focus anchor hierarchies.
In this repository87 other files it gives its agents.
- CLAUDE.mdCLAUDE.md
- motion-designSkill
- 3d-transformsSkill
- accessibilitySkill
- android-nativeSkill
- animation-recipesSkill
- anticipationSkill
- arc-motionSkill
- attentionSkill
- audio-reactiveSkill
- blurSkill
- brand-animationSkill
- cameraSkill
- characterSkill
- choice-resolutionSkill
- cinematicSkill
- code-generationSkill
- colorSkill
- component-animationsSkill
- compositingSkill
- consequenceSkill
- container-transformSkill
- css-advancedSkill
- data-visualizationSkill
- depthSkill
- distortionSkill
- dramaturgySkill
- durationSkill
- easingSkill
- emotional-journeySkill
- emotion-mappingSkill
- emotion-selectionSkill
- empty-statesSkill
- entrance-patternsSkill
- exaggerationSkill
- exit-patternsSkill
- exportSkill
- figma-integrationSkill
- follow-throughSkill
- frame-timingSkill
- gesture-motionSkill
- glow-shadowSkill
- intentSkill
- ios-nativeSkill
- loading-statesSkill
- loopingSkill
- lottieSkill
- masksSkill
- microstateSkill
- morphingSkill
- motion-auditSkill
- motion-blurSkill
- motion-principlesSkill
- motion-specsSkill
- narrative-arcSkill
- notificationsSkill
- onboardingSkill
- opacitySkill
- orchestratorSkill
- particlesSkill
- The first 60 shown.
The file
Files
Attention Choreography
Guide visual attention through animation sequencing. Align animation order with natural reading patterns and visual hierarchy to create intuitive, scannable motion.
Standards Reference
| Standard | Documentation |
| W3C DTCG | designtokens.org |
| Nielsen Norman Group | F-pattern, Z-pattern research |
| Material Design 3 | m3.material.io/styles/motion |
| Gutenberg Diagram | Visual gravity principles |
Quick Reference
Load from: tokens/attention-path.tokens.json
Reading Patterns
| Pattern | Primary Axis | Best For | Stagger |
| F-pattern | Horizontal | Text-heavy, articles | 60ms |
| Z-pattern | Diagonal | Landing pages, sparse | 80ms |
| center-out | Radial | Hero sections, modals | 50ms |
| gutenberg | Diagonal | Forms, cards | 70ms |
| layer-depth | Z-axis | Parallax, 3D scenes | 100ms |
Focus Anchors
| Level | Attention Weight | Animation Priority |
| primary | 1.0 | 1 (first) |
| secondary | 0.6 | 2 |
| tertiary | 0.3 | 3 |
| ambient | 0.1 | 4 (last) |
Selection Guide
What's the content type?
├── Text-heavy content (articles, docs)
│ └── F-PATTERN
│ └── Animate top-left → top-right → down-left
│
├── Sparse, marketing-focused (landing)
│ └── Z-PATTERN
│ └── Animate top-left → top-right → bottom-left → CTA
│
├── Focal point content (hero, modal)
│ └── CENTER-OUT
│ └── Animate center → inner ring → outer ring
│
├── Balanced layout (forms, cards)
│ └── GUTENBERG
│ └── Primary optical → strong fallow → terminal
│
└── Layered/3D content
└── LAYER-DEPTH
└── Foreground → midground → backgroundPattern Implementations
F-Pattern
For text-heavy content with strong horizontal bias.
// F-pattern: top-left emphasis, decreasing attention downward
const fPatternConfig = {
zones: {
'top-left': { priority: 1, stagger: 0 },
'top-right': { priority: 2, stagger: 60 },
'mid-left': { priority: 3, stagger: 120 },
'mid-right': { priority: 4, stagger: 180 },
'bottom-left': { priority: 5, stagger: 240 }
},
attentionDecay: 0.15
};
function applyFPattern(elements) {
const zones = categorizeByPosition(elements);
Object.entries(fPatternConfig.zones).forEach(([zone, config]) => {
const zoneElements = zones[zone] || [];
gsap.from(zoneElements, {
opacity: 0,
y: 20,
duration: 0.3,
delay: config.stagger / 1000,
stagger: 0.03,
ease: 'power2.out'
});
});
}
function categorizeByPosition(elements) {
const zones = {};
const container = elements[0].parentElement.getBoundingClientRect();
elements.forEach(el => {
const rect = el.getBoundingClientRect();
const relX = (rect.left - container.left) / container.width;
const relY = (rect.top - container.top) / container.height;
let zone;
if (relY < 0.33) {
zone = relX < 0.5 ? 'top-left' : 'top-right';
} else if (relY < 0.66) {
zone = relX < 0.5 ? 'mid-left' : 'mid-right';
} else {
zone = 'bottom-left';
}
if (!zones[zone]) zones[zone] = [];
zones[zone].push(el);
});
return zones;
}Framer Motion:
const fPatternVariants = {
hidden: {},
visible: {
transition: {
staggerChildren: 0.06,
staggerDirection: 1
}
}
};
// Assign data-priority based on position
<motion.div variants={fPatternVariants}>
<motion.h1 data-priority="1" variants={itemVariants}>Headline</motion.h1>
<motion.nav data-priority="2" variants={itemVariants}>Navigation</motion.nav>
<motion.p data-priority="3" variants={itemVariants}>Content</motion.p>
</motion.div>Z-Pattern
For landing pages and sparse layouts.
const zPatternConfig = {
path: ['top-left', 'top-right', 'center', 'bottom-left', 'bottom-right'],
emphasisZones: ['top-left', 'bottom-right'], // Logo and CTA
staggerBase: 80
};
function applyZPattern(elements) {
const zones = categorizeForZ(elements);
let cumulativeDelay = 0;
zPatternConfig.path.forEach((zone, index) => {
const zoneElements = zones[zone] || [];
const isEmphasis = zPatternConfig.emphasisZones.includes(zone);
gsap.from(zoneElements, {
opacity: 0,
scale: isEmphasis ? 0.9 : 0.95,
y: isEmphasis ? 30 : 15,
duration: isEmphasis ? 0.4 : 0.3,
delay: cumulativeDelay / 1000,
ease: isEmphasis ? 'back.out(1.5)' : 'power2.out'
});
cumulativeDelay += zPatternConfig.staggerBase;
});
}Center-Out
For hero sections and modals with central focus.
const centerOutConfig = {
rings: ['center', 'inner-ring', 'outer-ring', 'edges'],
staggerPerRing: 50,
intensityDecay: 0.2
};
function applyCenterOut(elements, containerCenter) {
const rings = categorizeByDistance(elements, containerCenter);
rings.forEach((ring, ringIndex) => {
const intensity = 1 - (ringIndex * centerOutConfig.intensityDecay);
gsap.from(ring, {
opacity: 0,
scale: 0.8 + (0.2 * (1 - intensity)),
duration: 0.3 + (0.1 * intensity),
delay: ringIndex * centerOutConfig.staggerPerRing / 1000,
ease: 'power2.out'
});
});
}
function categorizeByDistance(elements, center) {
const distances = elements.map(el => {
const rect = el.getBoundingClientRect();
const elCenter = {
x: rect.left + rect.width / 2,
y: rect.top + rect.height / 2
};
return {
element: el,
distance: Math.hypot(elCenter.x - center.x, elCenter.y - center.y)
};
});
// Sort by distance and group into rings
distances.sort((a, b) => a.distance - b.distance);
const ringSize = Math.ceil(elements.length / 4);
const rings = [];
for (let i = 0; i < 4; i++) {
rings.push(
distances
.slice(i * ringSize, (i + 1) * ringSize)
.map(d => d.element)
);
}
return rings;
}Framer Motion:
function CenterOutContainer({ children }) {
const containerRef = useRef(null);
return (
<motion.div
ref={containerRef}
variants={{
hidden: {},
visible: {
transition: {
staggerChildren: 0.05,
staggerDirection: 1
}
}
}}
>
{React.Children.map(children, (child, index) => {
// Calculate ring based on distance from center
const ring = Math.floor(index / 3); // Simplified
return (
<motion.div
variants={{
hidden: { opacity: 0, scale: 0.8 },
visible: {
opacity: 1,
scale: 1,
transition: { delay: ring * 0.05 }
}
}}
>
{child}
</motion.div>
);
})}
</motion.div>
);
}Gutenberg Diagram
For balanced layouts following natural visual flow.
const gutenbergConfig = {
zones: {
'primary-optical': { position: 'top-left', weight: 1.0 },
'strong-fallow': { position: 'top-right', weight: 0.6 },
'weak-fallow': { position: 'bottom-left', weight: 0.3 },
'terminal-area': { position: 'bottom-right', weight: 0.9 }
},
readingGravity: 'diagonal'
};
function applyGutenberg(elements) {
const zones = categorizeGutenberg(elements);
// Primary optical area first
gsap.from(zones['primary-optical'], {
opacity: 0,
y: 20,
duration: 0.4,
ease: 'power2.out'
});
// Strong fallow (often navigation)
gsap.from(zones['strong-fallow'], {
opacity: 0,
y: 15,
duration: 0.3,
delay: 0.08,
ease: 'power2.out'
});
// Terminal area (CTA)
gsap.from(zones['terminal-area'], {
opacity: 0,
scale: 0.95,
duration: 0.35,
delay: 0.15,
ease: 'back.out(1.5)'
});
// Weak fallow last
gsap.from(zones['weak-fallow'], {
opacity: 0,
duration: 0.25,
delay: 0.2,
ease: 'power2.out'
});
}Focus Anchors
Establish visual hierarchy with explicit priority.
interface FocusAnchor {
element: HTMLElement;
level: 'primary' | 'secondary' | 'tertiary' | 'ambient';
attentionWeight: number;
entrancePriority: number;
}
const focusLevels = {
primary: { weight: 1.0, priority: 1, intensity: 0.8 },
secondary: { weight: 0.6, priority: 2, intensity: 0.6 },
tertiary: { weight: 0.3, priority: 3, intensity: 0.4 },
ambient: { weight: 0.1, priority: 4, intensity: 0.2 }
};
function animateByHierarchy(anchors: FocusAnchor[]) {
// Sort by priority
anchors.sort((a, b) => a.entrancePriority - b.entrancePriority);
let cumulativeDelay = 0;
anchors.forEach(anchor => {
const config = focusLevels[anchor.level];
gsap.from(anchor.element, {
opacity: 0,
y: 20 * config.intensity,
scale: 1 - (0.1 * config.intensity),
duration: 0.3,
delay: cumulativeDelay,
ease: 'power2.out'
});
cumulativeDelay += 0.08;
});
}Attention Shifts
Transitioning focus between elements.
Glide Shift
Smooth attention transfer.
function glideShift(from, to) {
const tl = gsap.timeline();
// Defocus current
tl.to(from, {
opacity: 0.6,
scale: 0.98,
duration: 0.15
});
// Focus new
tl.to(to, {
opacity: 1,
scale: 1,
boxShadow: '0 0 0 3px rgba(33, 150, 243, 0.3)',
duration: 0.2
}, '-=0.1');
return tl;
}Bridge Shift
Connected path between focus points.
function bridgeShift(from, to) {
const fromRect = from.getBoundingClientRect();
const toRect = to.getBoundingClientRect();
// Create visual connector
const connector = createConnector(fromRect, toRect);
const tl = gsap.timeline();
// Draw connector
tl.from(connector, {
strokeDashoffset: connector.getTotalLength(),
duration: 0.3,
ease: 'power2.inOut'
});
// Shift focus
tl.to(from, { opacity: 0.5, duration: 0.15 }, '-=0.15');
tl.to(to, { opacity: 1, scale: 1.02, duration: 0.2 });
// Fade connector
tl.to(connector, { opacity: 0, duration: 0.2 });
return tl;
}Distraction Management
Reduce competing attention during focus moments.
function suppressSecondary(primarySelector) {
// Dim non-primary elements
gsap.to('*:not(' + primarySelector + ')', {
opacity: 0.6,
filter: 'blur(1px)',
duration: 0.2,
ease: 'power2.out'
});
// Enhance primary
gsap.to(primarySelector, {
opacity: 1,
filter: 'blur(0)',
boxShadow: '0 4px 20px rgba(0,0,0,0.15)',
duration: 0.3
});
}
function restoreAll() {
gsap.to('*', {
opacity: 1,
filter: 'blur(0)',
boxShadow: 'none',
duration: 0.3
});
}Cultural Adaptations
RTL (Right-to-Left) Languages
function getPatternForDirection(pattern, direction) {
if (direction === 'rtl') {
const mirrorMap = {
'f-pattern': {
'top-left': 'top-right',
'top-right': 'top-left',
'mid-left': 'mid-right',
'mid-right': 'mid-left'
},
'z-pattern': {
'top-left': 'top-right',
'top-right': 'top-left',
'bottom-left': 'bottom-right',
'bottom-right': 'bottom-left'
}
};
return mirrorMap[pattern] || pattern;
}
return pattern;
}Reduced Motion
For prefers-reduced-motion: reduce:
| Full Choreography | Reduced |
| Staggered by pattern | Simultaneous fade |
| Attention shifts | Instant focus change |
| Bridge connectors | Skip |
| Hierarchy animation | Uniform timing |
const prefersReducedMotion = window.matchMedia(
'(prefers-reduced-motion: reduce)'
).matches;
if (prefersReducedMotion) {
// Skip choreography, simple fade
gsap.from(elements, {
opacity: 0,
duration: 0.2,
stagger: 0 // No stagger
});
}Cross-References
- See
sequencing/for stagger patterns - See
narrative-arc/for story-aligned timing - See
dramaturgy/for scene composition - See
entrance-patterns/for element entrance animations
References
references/f-pattern.md— F-pattern implementation detailsreferences/z-pattern.md— Z-pattern implementation details
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

