Skip to main content 首页 创作者 automattic design-polish-suite motion-and-micro-interactions
motion-and-micro-interactions Apply the motion language of premium VC-funded sites — specific cubic-bezier easings (not ease-in-out), snappy 75-200ms durations, mechanical press feedback, orchestrated hover deltas, width-animated buttons, mouse-tracking gradients, blur-in reveals, scroll-linked reveals. Use when interactions feel "springy and generic" or when the user wants their site to feel responsive, alive, physical.
跳到安装 Skills Marketplace 发现并探索由社区构建的 Agent Skills
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/Automattic/design-polish-suite --skill motion-and-micro-interactions命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
下载 Zip 下载中... name motion-and-micro-interactions description Apply the motion language of premium VC-funded sites — specific cubic-bezier easings (not ease-in-out), snappy 75-200ms durations, mechanical press feedback, orchestrated hover deltas, width-animated buttons, mouse-tracking gradients, blur-in reveals, scroll-linked reveals. Use when interactions feel "springy and generic" or when the user wants their site to feel responsive, alive, physical.
Motion and Micro-Interactions
Premium sites don't use "ease-in-out 300ms" — they use specific easings, deliberate durations, orchestrated hover states, and physical press feedback. Extracted from Amplemarket, Cohere, Antimetal, Hex, Autosend, Shade, Cal, Amplemarket, AngelList.
When to invoke
Transitions use transition: all 300ms ease-in-out defaults
Buttons have hover:bg-blue-600 only — no press, no scale, no delta beyond color
Scroll doesn't do anything
Cursor feels static on the page
User says design feels "flat," "lifeless," "not responsive"
The 7 core rules
Rule 1: Stop using ease-in-out — pick a specific cubic-bezier
Case-study sites use specific, named cubic-bezier curves , not browser defaults:
--ease-amplemarket : cubic-bezier (.215 , .61 , .355 , 1 );
--ease-material : cubic-bezier (.4 , 0 , . , );
: (. , . , . , );
: ( , , . , );
: (. , , . , );
: (. , , . , );
: ( , , . , );
2
1
--ease-hex
cubic-bezier
25
1
25
1
--ease-out-tw
cubic-bezier
0
0
2
1
--ease-framer
cubic-bezier
44
0
56
1
--ease-in-out-reducto
cubic-bezier
4
0
2
1
--ease-out-reducto
cubic-bezier
0
0
2
1
Rule of thumb : use cubic-bezier(.4, 0, .2, 1) as your default. Use cubic-bezier(.215, .61, .355, 1) for "pop-in" entrances. Use cubic-bezier(0, 0, .2, 1) for anything that needs to feel snappy.
Anti-pattern: transition: all 0.3s ease-in-out; everywhere.
Rule 2: Durations matter — 75ms is tactile, 200ms is calm, 1500ms is editorial The default 300ms is lazy. Premium sites pick specific durations per interaction type:
Duration Use case Source 75ms Button press/active state Autosend (ease-in duration-75) 100-150ms Hover color swaps, icon arrows Reducto, Antimetal (--default-transition-duration: .15s) 200ms Card hover, background shifts Cal, Incident, most sites 250ms Button color + ring delta Hex, Incident 300ms Color-only transitions Reducto transition-colors duration-300 500ms Scroll-triggered reveals, parallax Amplemarket 1500ms Slow opacity fade-in (editorial) Cohere (opacity 1.5s ease-in)
Button press should be FAST (75-100ms) . Hover deltas can be 200ms. Scroll reveals can breathe at 400-600ms.
Rule 3: Buttons need mechanical press feedback hover:bg-blue-600 is not enough. Give the button a physical pressed state .
Autosend's mechanical press (copy this):
.btn {
transition : all 75ms ease-in;
}
.btn :active {
box-shadow : none;
transform : scale (0.95 );
}
Amplemarket's inset-inversion press — swaps the inset highlight from white to dark so the button LOOKS pushed in:
.btn {
box-shadow :
0 6px 2px #11111103 ,
0 3px 2px #1111110D ,
0 1px 1px #11111117 ,
0 0 1px #1111111f ,
inset 0 6px 12px #ffffff33 ,
inset 0 1px 1px #fff3 ;
}
.btn :active {
box-shadow :
0 6px 2px #11111103 ,
0 3px 2px #1111110D ,
0 1px 1px #11111117 ,
0 0 1px #1111111f ,
inset 0 6px 12px #11111133 ,
inset 0 1px 1px #111111f3 ;
}
Antimetal's orchestrated hover — sub-elements physically translate apart:
.cta-button :hover .cta-label { transform : translateX (-36px ); }
.cta-button :hover .cta-arrow { transform : translateX (36px ); }
.cta-button :hover { width : 102% ; }
Cohere's width-animated button — the button itself expands on hover:
.primary-btn {
max-width : 200px ;
transition : max-width .2s ease-in-out;
}
.primary-btn :hover { max-width : 300px ; }
Anti-pattern: .btn:hover { background: darker; } (color-only delta).
Rule 4: Hero text reveals with blur-in, not opacity fade Standard AI reveal: opacity: 0 → opacity: 1 (boring).
Antimetal's reveal: vertical rise + blur clearing + opacity.
.hero-line {
opacity : 0 ;
transform : translateY (48px );
filter : blur (4px );
transition : opacity 600ms , transform 600ms , filter 600ms ;
transition-timing-function : cubic-bezier (.215 , .61 , .355 , 1 );
}
.hero-line .visible {
opacity : 1 ;
transform : translateY (0 );
filter : blur (0 );
}
.hero-line :nth-child (1 ) { transition-delay : 0ms ; }
.hero-line :nth-child (2 ) { transition-delay : 100ms ; }
.hero-line :nth-child (3 ) { transition-delay : 200ms ; }
Rule 5: Cards hover with shadow growth, not translateY Most sites don't lift cards — they grow the shadow + ring weight :
Cal's hover — ring darkens from 8% to 10% alpha:
.card {
box-shadow :
0px 1px 5px -4px rgba (19 ,19 ,22 ,0.7 ),
0px 0px 0px 1px rgba (34 ,42 ,53 ,0.08 ),
0px 4px 8px 0px rgba (34 ,42 ,53 ,0.05 );
transition : box-shadow 200ms ;
}
.card :hover {
box-shadow :
0px 1px 5px -4px rgba (19 ,19 ,22 ,0.7 ),
0px 0px 0px 1px rgba (34 ,42 ,53 ,0.10 ),
0px 4px 8px 0px rgba (34 ,42 ,53 ,0.05 );
}
Hex's hover — ring weight and contact shadow deepen:
.card {
box-shadow :
0 0 0 1px rgba (49 ,38 ,59 ,0.22 ),
0 103px 103px 0 rgba (49 ,38 ,59 ,0.09 ),
0 26px 57px 0 rgba (49 ,38 ,59 ,0.10 );
}
.card :hover {
box-shadow :
0 0 0 1px rgba (49 ,38 ,59 ,0.32 ),
0 103px 103px 0 rgba (49 ,38 ,59 ,0.12 ),
0 26px 57px 0 rgba (49 ,38 ,59 ,0.15 );
}
Incident's hover — shadow-lg → shadow-2xl:
.card { box-shadow : var (--tw-shadow-lg); transition : box-shadow 250ms ; }
.card :hover { box-shadow : var (--tw-shadow-2 xl); }
Anti-pattern: .card:hover { transform: translateY(-4px); } (generic).
Rule 6: Mouse-tracking effects create living pages Amplemarket — the hero text is a radial-gradient whose position follows the mouse via CSS variables:
.hero-text {
background : radial-gradient (
80% 109% at var (--gradient-posX, 50% ) var (--gradient-posY, 50% ),
#D0B2FF 0% ,
#FFEED8 50% ,
#E8400D 100% );
background-clip : text;
color : transparent;
}
document .addEventListener ('mousemove' , (e ) => {
const { clientX, clientY, currentTarget } = e;
const { width, height } = currentTarget.getBoundingClientRect ();
document .documentElement .style .setProperty ('--gradient-posX' , `${(clientX/width)*100 } %` );
document .documentElement .style .setProperty ('--gradient-posY' , `${(clientY/height)*100 } %` );
});
Shade — a chartreuse (#c6eb57) spotlight follows the cursor over the hero. Plus a conic-gradient halo that spins around the AI search lozenge:
.ai-halo {
background : conic-gradient (transparent 200deg , rgb (133 , 92 , 247 ));
animation : spin 2s linear infinite;
}
@keyframes spin { to { transform : rotate (360deg ); } }
Rule 7: Scroll-triggered reveals per section (not per element) Premium sites reveal content in staggered groups with a subtle delta, not per-element fades:
.section {
opacity : 0 ;
transform : translateY (24px );
transition : opacity 600ms cubic-bezier (.215 ,.61 ,.355 ,1 ) 0ms ,
transform 600ms cubic-bezier (.215 ,.61 ,.355 ,1 ) 0ms ;
}
.section .in-view {
opacity : 1 ;
transform : translateY (0 );
}
.section .reveal-item { transition-delay : calc (var (--index, 0 ) * 80ms ); }
const observer = new IntersectionObserver ((entries ) => {
entries.forEach ((entry ) => {
if (entry.isIntersecting ) entry.target .classList .add ('in-view' );
});
}, { threshold : 0.15 });
document .querySelectorAll ('.section' ).forEach ((el ) => observer.observe (el));
AngelList goes further with scroll-linked parallax and pinned sections — ScrollTrackingWrapper drives per-section reveals with 2-5 second timelines.
Incident's hero rotates through 5 product screenshots via Swiper — the real product UI is the motion.
Complete motion system starter :root {
--ease-snappy : cubic-bezier (.215 , .61 , .355 , 1 );
--ease-standard : cubic-bezier (.4 , 0 , .2 , 1 );
--ease-out : cubic-bezier (0 , 0 , .2 , 1 );
--ease-framer : cubic-bezier (.44 , 0 , .56 , 1 );
--duration-press : 75ms ;
--duration-quick : 150ms ;
--duration-base : 200ms ;
--duration-reveal : 600ms ;
--duration-slow : 1500ms ;
}
.btn {
transition : all var (--duration-press) ease-in;
}
.btn :hover {
transition-duration : var (--duration-base);
}
.btn :active {
transform : scale (0.97 );
}
.card {
transition : box-shadow var (--duration-base) var (--ease-standard);
}
@keyframes reveal-up {
from { opacity : 0 ; transform : translateY (48px ); filter : blur (4px ); }
to { opacity : 1 ; transform : translateY (0 ); filter : blur (0 ); }
}
.hero-line {
animation : reveal-up var (--duration-reveal) var (--ease-snappy) both;
}
.hero-line :nth-child (1 ) { animation-delay : 0ms ; }
.hero-line :nth-child (2 ) { animation-delay : 100ms ; }
.hero-line :nth-child (3 ) { animation-delay : 200ms ; }
.section {
opacity : 0 ;
transform : translateY (24px );
transition : opacity var (--duration-reveal) var (--ease-snappy),
transform var (--duration-reveal) var (--ease-snappy);
}
.section .in-view {
opacity : 1 ;
transform : translateY (0 );
}
.hero-spotlight {
background : radial-gradient (
250px circle at var (--mx, 50% ) var (--my, 50% ),
rgba (198 , 235 , 87 , 0.4 ),
transparent 40% );
pointer-events : none;
}
Application workflow
Replace every ease-in-out with one of the 4 named cubic-beziers above.
Set duration by interaction type : 75ms active, 150-200ms hover, 600ms reveal.
Give buttons an :active state with scale(0.95) or inset-inversion.
Hover cards via shadow , not translateY.
Add blur-in reveals to hero text lines with staggered delays.
Add IntersectionObserver section reveals with 80ms stagger between children.
Consider a mouse-tracking effect for the hero (spotlight, gradient position, or tilt).
Anti-patterns to kill
transition: all 300ms ease-in-out on everything
.btn:hover { background-color: darker; } — color-only hover with no press
Cards that translateY(-4px) on hover (cliché)
opacity: 0 → 1 fades with no translateY or blur
Transitions that animate all properties (perf + visual noise)
Hero animations that don't stagger
Missing :active state on buttons (breaks physical feel)
Animating transform: translateX(...) with a generic 300ms ease
Site references Pattern Case study cubic-bezier(.215,.61,.355,1) easeOutCubic Amplemarket 75ms snappy button transitions Autosend 1.5s slow opacity fade Cohere Inset-inversion press feedback Amplemarket Width-animated button Cohere (max-width) Orchestrated child translation on hover Antimetal Blur-in hero reveal Antimetal (blur-[4px] → 0) Shadow-grow card hover Cal, Hex, Incident Mouse-tracking gradient (CSS var) Amplemarket (WebGL), Shade (cursor spotlight) Conic-gradient halo spin Shade Scroll-linked per-section timelines AngelList (ScrollTrackingWrapper) Kbd keycap 3D shadow Incident (box-shadow: 0 3px 0)