From supervibe
Use WHEN designing micro-interactions, animations, transitions, loading states to apply timing tiers, easing rules, and WOW-effect patterns from current 2026 design practice. Triggers: 'добавь анимацию', 'нужны переходы', 'микроинтеракция', 'оживи интерфейс'.
npx claudepluginhub vtrka/supervibe --plugin supervibeThis skill is limited to using the following tools:
Before selecting interaction patterns, run project memory, code search, and internal `supervibe:design-intelligence` lookup for app-interface, UX, stack, accessibility, and performance guidance. Lookup is advisory and must respect approved motion tokens and reduced-motion rules.
Mandates invoking relevant skills via tools before any response in coding sessions. Covers access, priorities, and adaptations for Claude Code, Copilot CLI, Gemini CLI.
Share bugs, ideas, or general feedback.
Before selecting interaction patterns, run project memory, code search, and internal supervibe:design-intelligence lookup for app-interface, UX, stack, accessibility, and performance guidance. Lookup is advisory and must respect approved motion tokens and reduced-motion rules.
WHEN building UI that involves:
NOT for: pure static layout, content-only screens.
Follow docs/references/skill-expert-operating-standard.md: start from source of truth, preserve retrieval evidence, apply scope safety, use real producers with runtime receipts for durable delegated outputs, verify before completion claims, and keep confidence below gate when evidence is partial.
.supervibe/artifacts/prototypes/_design-system/motion.css for timing tiers + easing.supervibe/artifacts/prototypes/_design-system/components/ for component-specific transitionsprefers-reduced-motion policy (mandatory respect)node "<resolved-supervibe-plugin-root>/scripts/detect-media-capabilities.mjs" --json before promising video/GIF output. If video is unavailable, select CSS/WAAPI, SVG/Lottie spec, storyboard frames, or static poster alternatives.What kind of motion is this?
├─ Single property change between two states → CSS transition
├─ Repeating idle / decorative motion → CSS @keyframes
├─ Programmatic timing, dynamic values → Web Animations API (element.animate)
├─ Scroll position drives progress → Intersection Observer OR scroll-driven animations
├─ Layout change (DOM moved/reordered) → FLIP technique
├─ Cross-route element morph → View Transitions API (or shared-element FLIP)
├─ Natural physics feel (drag-release, modal) → Spring physics with rAF
├─ Stagger sequence of N children → CSS animation-delay OR JS forEach + delay
└─ Hero with thousands of elements / shaders → Canvas 2D OR WebGL/Three.js
What kind of interaction trigger?
├─ Hover/focus/active → CSS :hover/:focus-visible/:active + transition
├─ Click/tap with feedback → CSS :active + Web Animations API for richer feedback
├─ Pointer/touch gesture (drag, swipe) → Pointer Events + rAF loop
├─ Scroll position → Intersection Observer (entry/exit) or scroll-timeline
├─ Time-driven (idle decorative) → CSS animation infinite OR rAF
└─ Intersection-driven (entrance reveal) → Intersection Observer + class toggle
How heavy can the implementation be?
├─ ≤3 animated elements, simple state → CSS-only (zero JS)
├─ Choreographed sequence, ≤200 lines logic → Web Animations API (no library)
├─ Spring physics, drag, complex orchestration → Library (Motion One / GSAP)
├─ 1000+ elements / custom rendering → Canvas 2D
└─ 3D, shaders, particle counts > 5k → WebGL / Three.js
Animation tier (purpose):
├─ Entrance — element first appears → ease-out, 250-450ms
├─ Exit — element leaves → ease-in, 150-250ms (faster than entrance)
├─ Idle — ambient decorative motion → infinite, very subtle, low contrast
├─ Attention — call user focus (error, new msg) → 1-2 oscillations max, then settle
└─ State-change — toggle, hover, value change → match tier 1-2 timing
prefers-reduced-motion?
├─ ALWAYS respect — provide instant fallback or short fade
└─ Test: emulate "reduce motion" in browser, confirm no large translation/scale runs
/* === Standard easings === */
--ease-linear: cubic-bezier(0, 0, 1, 1); /* almost never correct */
--ease-out-quad: cubic-bezier(0.25, 0.46, 0.45, 0.94); /* gentle deceleration; default for state changes */
--ease-in-out-quart: cubic-bezier(0.77, 0, 0.175, 1); /* dramatic balanced; modal open/close */
--ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1); /* fast then slow; hero reveals, page transitions */
--ease-in-expo: cubic-bezier(0.7, 0, 0.84, 0); /* exit animations */
--ease-out-back: cubic-bezier(0.34, 1.56, 0.64, 1); /* slight overshoot; playful enters */
--ease-spring-soft: cubic-bezier(0.5, 1.25, 0.5, 1); /* approximate spring without rAF */
--ease-material: cubic-bezier(0.4, 0, 0.2, 1); /* Material standard; safe default */
WHEN to use each:
--ease-out-quad — buttons, hovers, dropdowns; user expects quick acknowledgement, gentle settle--ease-in-out-quart — modals, drawers; symmetric importance on enter and exit--ease-out-expo — page transitions, hero entrances; dramatic but not bouncy--ease-in-expo — exits, fade-outs (fast departure feels responsive)--ease-out-back — playful brand moments, success confirmations (the slight overshoot reads as "joy")--ease-spring-soft — approximation when you can't afford a rAF spring loop--ease-material — when you don't know which to pick; safe defaultWHY linear is almost always wrong: real-world motion has inertia. Linear motion reads as mechanical, robotic, "fake". Use linear ONLY for: progress bars (linear time), looping rotations (no perceptible end), gradient sweeps in idle decorations.
WHY ease-out beats ease-in for user-initiated transitions: when user clicks, they expect immediate response. ease-out starts fast (acknowledgement), slows down (graceful settle). ease-in starts slow → feels laggy and unresponsive.
Spring physics tuning (when using rAF spring or Motion One):
mass (1.0 default) — higher = heavier, slower start; UI elements rarely > 1.5tension / stiffness (170-300 typical) — higher = snappier, faster oscillationfriction / damping (20-40 typical) — higher = less wobble; for serious UI use damping ratio ≥ 0.7 (no overshoot)| Tier | Duration | Use case | Default easing |
|---|---|---|---|
| instant | 0-100ms | hover state, focus ring, color shift | ease-out |
| quick | 150-250ms | button press, tooltip, dropdown, toast | ease-out-quad |
| considered | 250-450ms | modal, drawer, page section reveal, accordion | ease-in-out-quart |
| deliberate | 450-700ms | hero entrance, staged sequence, route transition | ease-out-expo |
| narrative | 700ms+ | onboarding, landing storytelling, scene change | custom per moment |
Rule of thumb: every 100ms of duration past 250ms must earn its place. Animation duration is cost paid by every user on every interaction — and they pay it forever.
What: declarative keyframe-based animation, runs on compositor thread.
When: looping idle motion (pulsing dot, gentle float, gradient shimmer).
Performance: free if animating transform/opacity; expensive if animating layout properties.
Common bugs: forgetting animation-fill-mode: both causes flash on start; infinite loops with alternate direction skip the from-keyframe on reversal.
@keyframes float {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-8px); }
}
.hero-card {
animation: float 4s ease-in-out infinite;
animation-fill-mode: both;
will-change: transform; /* hint, remove after animation if one-shot */
}
@media (prefers-reduced-motion: reduce) {
.hero-card { animation: none; }
}
What: animate property changes triggered by class/state toggle.
When: hover/focus/active, class toggles for show/hide, theme switches.
Performance: ideal for transform, opacity, color, filter.
Common bugs: transitioning display doesn't work — use visibility + opacity; transitioning to auto height fails — use max-height or grid trick.
.btn {
background: hsl(220 90% 56%);
transform: translateY(0);
transition:
background 150ms var(--ease-out-quad),
transform 100ms var(--ease-out-quad),
box-shadow 200ms var(--ease-out-quad);
}
.btn:hover { background: hsl(220 90% 62%); transform: translateY(-1px); box-shadow: 0 6px 20px rgb(0 0 0 / 0.15); }
.btn:active { transform: translateY(0); transition-duration: 50ms; }
What: element.animate(keyframes, options) — JS-driven, returns Animation handle (pause/cancel/finished promise).
When: dynamic values from JS, sequential chaining via promise, animations triggered by data changes.
Performance: same compositor thread as CSS; equivalent perf.
Common bugs: finished promise rejects on cancel — wrap in .catch(); composite: 'add' not supported in all browsers.
const card = document.querySelector('.card');
const anim = card.animate(
[
{ transform: 'translateY(20px)', opacity: 0 },
{ transform: 'translateY(0)', opacity: 1 },
],
{ duration: 350, easing: 'cubic-bezier(0.16, 1, 0.3, 1)', fill: 'both' }
);
await anim.finished.catch(() => {});
// chain next animation after this resolves
What: browser snapshots before+after states, cross-fades or runs custom CSS animation between them. Modern Chrome/Edge (cross-document needs Chrome 126+), Safari 18+.
When: route transitions, list-to-detail morphs, large layout shifts.
Performance: snapshot costs paint once; the animation itself is GPU.
Common bugs: requires view-transition-name to be unique per concurrent transition; without fallback, Safari < 18 simply skips animation.
function navigateWithTransition(url) {
if (!document.startViewTransition) { location.href = url; return; }
document.startViewTransition(async () => {
const html = await fetch(url).then(r => r.text());
document.querySelector('main').innerHTML =
new DOMParser().parseFromString(html, 'text/html').querySelector('main').innerHTML;
});
}
::view-transition-old(root),
::view-transition-new(root) {
animation-duration: 350ms;
animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
}
.hero-image { view-transition-name: hero; } /* persist this element across navigation */
What: observer fires when element enters viewport; toggle class triggers CSS animation.
When: scroll-triggered reveals, lazy animations, cards appearing as user scrolls.
Performance: no scroll handler — observer is browser-optimized.
Common bugs: forgetting threshold produces flicker at edges; not unobserving after first reveal wastes cycles.
<div class="reveal">Content</div>
<style>
.reveal { opacity: 0; transform: translateY(24px);
transition: opacity 500ms var(--ease-out-expo), transform 500ms var(--ease-out-expo); }
.reveal.is-visible { opacity: 1; transform: translateY(0); }
@media (prefers-reduced-motion: reduce) {
.reveal { opacity: 1; transform: none; transition: none; }
}
</style>
<script>
const io = new IntersectionObserver((entries) => {
for (const e of entries) if (e.isIntersecting) {
e.target.classList.add('is-visible');
io.unobserve(e.target); // one-shot
}
}, { threshold: 0.15, rootMargin: '0px 0px -10% 0px' });
document.querySelectorAll('.reveal').forEach(el => io.observe(el));
</script>
What: animation-timeline: view() or scroll() ties keyframe progress to scroll position. Chrome 115+, Firefox not yet (April 2026); needs feature detection + fallback.
When: parallax, progress bars, scroll-linked transforms — without JS.
Performance: compositor-only; no rAF cost.
Common bugs: view() ranges (cover, entry, exit) commonly confused; always test on real content scroll.
@supports (animation-timeline: view()) {
.parallax-image {
animation: parallax linear;
animation-timeline: view();
animation-range: entry 0% exit 100%;
}
@keyframes parallax {
from { transform: translateY(-15%); }
to { transform: translateY(15%); }
}
}
/* fallback: static image, no parallax */
What: integrate spring equation each frame; outputs natural feel for drag-release, modal pop-in. When: physics-feel matters more than determinism (drag-and-release, dismiss gestures). Performance: rAF runs every frame on main thread; keep loop body lean. Common bugs: floating-point creep — terminate when |velocity| < epsilon AND |position - target| < epsilon.
function spring(from, to, { stiffness = 170, damping = 26, mass = 1, onUpdate, onDone }) {
let x = from, v = 0, last = performance.now();
const tick = (now) => {
const dt = Math.min(0.064, (now - last) / 1000); last = now;
const Fspring = -stiffness * (x - to);
const Fdamp = -damping * v;
const a = (Fspring + Fdamp) / mass;
v += a * dt; x += v * dt;
onUpdate(x);
if (Math.abs(v) < 0.01 && Math.abs(x - to) < 0.01) { onUpdate(to); onDone?.(); return; }
requestAnimationFrame(tick);
};
requestAnimationFrame(tick);
}
spring(0, 100, { onUpdate: (x) => card.style.transform = `translateY(${x}px)` });
What: First (measure), Last (move/change), Invert (translate back to original), Play (animate to identity). Animates layout changes via transform. When: list reorder, grid reflow, element changes parent. Performance: avoids layout thrash by animating cheap transform instead of expensive layout. Common bugs: forgetting to read both First and Last in same frame; transforms compound if not reset.
function flip(el, mutate) {
const first = el.getBoundingClientRect(); // F
mutate(); // L (DOM change)
const last = el.getBoundingClientRect();
const dx = first.left - last.left, dy = first.top - last.top;
el.animate( // I + P
[
{ transform: `translate(${dx}px, ${dy}px)` },
{ transform: 'translate(0, 0)' },
],
{ duration: 400, easing: 'cubic-bezier(0.16, 1, 0.3, 1)' }
);
}
flip(card, () => listContainer.prepend(card));
What: same logical element (e.g. product image) animates from list view to detail view. When: list→detail flows where continuity matters (gallery → lightbox, product card → product page). Performance: View Transitions API native; FLIP-based fallback works everywhere. Common bugs: source element must still be in DOM at animation start; aspect ratio mismatches produce squish.
// Modern: View Transitions
async function openDetail(href, image) {
image.style.viewTransitionName = 'product-image';
if (!document.startViewTransition) { location.href = href; return; }
await document.startViewTransition(async () => { /* swap DOM */ }).finished;
}
// Fallback: FLIP-style — measure source, render destination, animate clone
function morphSharedElement(source, target) {
const s = source.getBoundingClientRect(), t = target.getBoundingClientRect();
const clone = source.cloneNode(true);
Object.assign(clone.style, { position: 'fixed', left: s.left + 'px', top: s.top + 'px',
width: s.width + 'px', height: s.height + 'px', margin: 0, transition: 'all 500ms cubic-bezier(0.16,1,0.3,1)' });
document.body.appendChild(clone);
requestAnimationFrame(() => {
Object.assign(clone.style, { left: t.left + 'px', top: t.top + 'px', width: t.width + 'px', height: t.height + 'px' });
});
clone.addEventListener('transitionend', () => clone.remove(), { once: true });
}
What: gray boxes shaped like content, with shimmer gradient sweeping across. When: any data fetch > 200ms; placeholders calm uncertainty. Performance: pure CSS — no JS overhead. Common bugs: skeleton shape doesn't match real content → visible reflow when data arrives.
.skeleton {
background: linear-gradient(90deg, #eee 0%, #f5f5f5 50%, #eee 100%);
background-size: 200% 100%;
animation: shimmer 1.4s linear infinite;
border-radius: 4px;
}
@keyframes shimmer {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
.skeleton.title { height: 24px; width: 60%; margin-bottom: 12px; }
.skeleton.line { height: 14px; width: 100%; margin-bottom: 8px; }
.skeleton.line.short { width: 80%; }
What: each child animates in with progressively delayed start. When: hero reveals, navigation menus, card grids on initial paint. Performance: pure CSS; computed once. Common bugs: hardcoded delays break when item count changes — use CSS custom property + nth-child OR JS index.
.list > * {
opacity: 0; transform: translateY(16px);
animation: fade-up 500ms cubic-bezier(0.16, 1, 0.3, 1) forwards;
animation-delay: calc(var(--i, 0) * 60ms);
}
@keyframes fade-up { to { opacity: 1; transform: translateY(0); } }
document.querySelectorAll('.list > *').forEach((el, i) => el.style.setProperty('--i', i));
What: prioritise visible-on-load elements, defer below-fold animations until visible. When: landing pages, hero sections, anywhere LCP matters. Performance: prevents jank during initial paint by NOT animating everything at once. Common bugs: large entrance animations delay LCP; heavy CSS animations on first paint hurt INP score.
// Phase 1 — immediate paint, no animation on critical-path text
document.documentElement.classList.add('is-loaded');
// Phase 2 — animate hero on next frame (allow paint to commit)
requestAnimationFrame(() => {
document.querySelector('.hero').classList.add('animate-in');
});
// Phase 3 — defer below-fold animations to Intersection Observer
const io = new IntersectionObserver((entries) => {
entries.forEach(e => e.isIntersecting && e.target.classList.add('animate-in'));
}, { threshold: 0.1 });
document.querySelectorAll('.below-fold').forEach(el => io.observe(el));
Rendered video is optional, not assumed. Before choosing video/WebM/GIF as a deliverable:
node "<resolved-supervibe-plugin-root>/scripts/detect-media-capabilities.mjs" --json.video=true, record encoding plan, poster frame, file budget, autoplay policy, captions if content-bearing, and reduced-motion fallback.video=false, choose one of:
assets/storyboard/background: linear-gradient(135deg, #4f46e5 0%, #06b6d4 50%, #10b981 100%);
background: radial-gradient(ellipse at top, rgba(99,102,241,.4), transparent 60%);
background: conic-gradient(from 90deg at 50% 50%, #f59e0b, #ef4444, #ec4899, #f59e0b);
background: g1, g2, g3;. Top first; transparent everywhere except where layer dominates.CSS-only 4-corner approximation:
.mesh {
background:
radial-gradient(at 0% 0%, hsla(282,86%,60%,1) 0px, transparent 50%),
radial-gradient(at 100% 0%, hsla(196,86%,60%,1) 0px, transparent 50%),
radial-gradient(at 100% 100%, hsla(335,86%,60%,1) 0px, transparent 50%),
radial-gradient(at 0% 100%, hsla(38,86%,60%,1) 0px, transparent 50%);
}
For richer mesh: pre-rendered SVG (export from Photoshop/Figma mesh-gradient plugin) saved as PNG/WebP; or libraries like mesh-gradient-svg for animated mesh.
.blob {
width: 400px; aspect-ratio: 1;
background: hsl(280 90% 70%);
border-radius: 63% 37% 54% 46% / 55% 48% 52% 45%; /* asymmetric percentages = organic */
animation: blob-morph 12s ease-in-out infinite alternate;
}
@keyframes blob-morph {
0% { border-radius: 63% 37% 54% 46% / 55% 48% 52% 45%; }
100% { border-radius: 38% 62% 67% 33% / 41% 70% 30% 59%; }
}
Workflow: design 2-3 keyframe shapes via blobmaker.app, drop the border-radius values into keyframes, animate.
<svg> — when you need CSS/JS access to fill/stroke/path; animation on individual paths possible.<use href="#icon">) — many copies of same icon set; one HTTP request, cacheable.<img src="x.svg"> — opaque blob, no CSS reach inside; smallest markup; for static logos.Animating SVG:
transform, stroke-dashoffset (line-draw effect), opacity on individual paths.<animate>, <animateMotion>) — declarative inside SVG; deprecated in Chrome at one point but currently supported; risky for forward compat.el.setAttribute('d', newPath) for path morphing.Line-draw SVG entrance:
.path { stroke-dasharray: 1000; stroke-dashoffset: 1000;
animation: draw 2s ease-out forwards; }
@keyframes draw { to { stroke-dashoffset: 0; } }
When worth it: 1000+ animated elements, custom rendering not expressible in CSS, generative art with per-frame logic.
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
const particles = Array.from({ length: 200 }, () => ({
x: Math.random() * innerWidth, y: Math.random() * innerHeight,
vx: (Math.random() - 0.5) * 0.5, vy: (Math.random() - 0.5) * 0.5, r: 1 + Math.random() * 2
}));
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = 'rgba(99,102,241,0.6)';
for (const p of particles) {
p.x += p.vx; p.y += p.vy;
if (p.x < 0 || p.x > canvas.width) p.vx *= -1;
if (p.y < 0 || p.y > canvas.height) p.vy *= -1;
ctx.beginPath(); ctx.arc(p.x, p.y, p.r, 0, Math.PI * 2); ctx.fill();
}
requestAnimationFrame(draw);
}
draw();
Use only when: 3D geometry, GLSL shaders for hero washes, particle counts > 5k, VR/AR. Otherwise the bundle cost (~150kb gzip Three.js core) and shader complexity are not justified.
Decision criteria:
<canvas> with shader (single quad + frag shader).When designer hands you JSON exported from After Effects via Bodymovin plugin. Player: lottie-web (~70kb gzip). Best for: one-shot delight moments, complex vector animations that would take days to recreate in CSS.
Do not paste a CDN player into a Supervibe prototype. If Lottie is needed,
record it in decisions/prototype-capability-plan.md, bundle or vendor the
approved runtime locally, lazy-load below the fold, provide a static poster, and
pause or replace the animation under prefers-reduced-motion: reduce.
For hero washes / generative art that CSS gradients can't express (noise, flow fields, moving fluid). Single fullscreen quad + frag shader:
// fragment shader (hero-wash.frag)
precision highp float;
uniform float u_time; uniform vec2 u_resolution;
void main() {
vec2 uv = gl_FragCoord.xy / u_resolution;
float n = sin(uv.x * 6.0 + u_time) * sin(uv.y * 4.0 - u_time * 0.7);
vec3 col = mix(vec3(0.31, 0.27, 0.90), vec3(0.02, 0.71, 0.84), n * 0.5 + 0.5);
gl_FragColor = vec4(col, 1.0);
}
.hero-image-wrapper { aspect-ratio: 16 / 9; width: 100%; }
.hero-image-wrapper img { width: 100%; height: 100%; object-fit: cover; object-position: center 30%; }
| Content | Format | Why |
|---|---|---|
| Photographs, hero imagery | AVIF (primary) → WebP (fallback) → JPEG | AVIF ~50% smaller than JPEG; WebP universal at this point |
| Screenshots, UI captures with text | PNG-8 or WebP-lossless | text edges suffer in lossy |
| Icons, logos, illustrations | SVG | vector scales, tiny, animatable |
| Animated graphics | WebM video > GIF | GIF averages 5-10x larger than equivalent WebM |
| Frame-by-frame UI animation | Lottie (JSON) | designer-friendly, scriptable |
Use <picture> for format negotiation:
<picture>
<source srcset="hero.avif" type="image/avif">
<source srcset="hero.webp" type="image/webp">
<img src="hero.jpg" alt="..." loading="lazy" decoding="async" width="1920" height="1080">
</picture>
transform and opacity ONLY for 60fps on cheap devices. These run on the compositor thread (skip layout + paint). Animating width/height/top/left/margin/padding triggers layout → paint → composite each frame; falls off 60fps quickly.will-change is a hint, not magic — it promotes element to its own GPU layer, costing memory. Overuse (e.g. * { will-change: transform; }) breaks GPU memory budget and slows everything. Apply just before animation, remove after.contain: layout style paint — isolates an element so its layout/paint can't ripple outward. Apply to animated containers to prevent triggering ancestor reflow.prefers-reduced-motion: reduce is mandatory for accessibility:
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important;
animation-iteration-count: 1 !important; scroll-behavior: auto !important; }
}
transform: translateZ(0) GPU promotion — old hack to force GPU layer; modern browsers usually do this automatically. Measure first; sometimes promotes too aggressively and hurts memory.animation-play-state: paused:
const io = new IntersectionObserver((entries) => {
entries.forEach(e => e.target.style.animationPlayState = e.isIntersecting ? 'running' : 'paused');
});
document.querySelectorAll('.always-animating').forEach(el => io.observe(el));
setInterval(16) is wrong (drifts off vsync, double-paints).| Need | Pick | Why |
|---|---|---|
| Pure CSS state transitions, ≤3 animated elements | None / native | No deps, smallest bundle |
| Choreographed sequences, gesture interactions, complex orchestration | GSAP | Most mature timeline API, smallest learning curve, robust browser support |
| Spring-physics-feel UI, vanilla JS | Motion One | ~12kb gzip, Web Animations API based, spring solver |
| Spring-physics-feel UI, React | Framer Motion / motion (forked) | Spring solver + React-native bindings |
| Lightweight tween, vanilla JS | Anime.js | 14kb gzip, simple API, mature |
| 3D / particles / shaders | Three.js | Industry standard, large ecosystem, docs |
| Pre-rendered designer JSON | Lottie-web | Direct After Effects export pipeline |
| Scroll-driven animations with fallback | GSAP ScrollTrigger | Robust polyfill where native scroll-timeline unavailable |
But for Supervibe prototypes default to native CSS + Web Animations API + Intersection Observer. Adding a library is escalation that requires a Prototype Capability Plan: "we need spring physics", "we have 50+ choreographed steps", "designer already exported Lottie JSON", "this chart needs interactive zoom", or "this 3D scene is the product proof." Do not pull GSAP for a fade-in, and do not use remote CDN runtime imports unless the plan explicitly marks the work handoff-only or documents a reviewed exception.
1. Cursor-following gradient — premium feel, subtle, ~10 lines JS
2. Magnetic buttons — cursor pulls button slightly; 1-2 px max
3. Stagger reveal on scroll — list items fade-up in sequence (60ms offset)
4. Shared element transition — element morphs from list to detail
5. Optimistic UI with success burst — instant feedback + checkmark/confetti
6. Skeleton with shimmer — perceived performance boost
7. Parallax depth — background slower than foreground (max 2 layers)
8. Confetti on conversion — first signup, purchase complete
9. Cursor-aware 3D tilt on cards — subtle depth (max ±5deg)
10. Page-load morph — splash element morphs into header logo
11. Hero shader wash — animated GLSL fragment shader behind hero
12. Mesh gradient hero background — animated radial-gradient blobs
prefers-reduced-motion: reduce fallbacksupervibe:preview-server skill with --root <output-dir> --daemon after files are written. Hand URL to user with hot-reload note.Returns:
Confidence: <N>.<dd>/10
Override: <true|false>
Rubric: prototype
width/height/top/left instead of transform — triggers layout each frame, drops to <30fps on cheap devices.prefers-reduced-motion — accessibility violation; vestibular-disorder users get sick.:hover on iOS sticks until next tap; provide touch-equivalent or focus-visible.will-change on every element — exhausts GPU memory; defeats the purpose; sometimes worse than not having it.load.asking-multiple-questions-at-once — bundling >1 question into one user message. ALWAYS one question with Step N/M: progress label.advancing-without-feedback-prompt — concluding delivery without printing the 5-choice feedback block (✅ / ✎ / 🔀 / 📊 / 🛑) and waiting for explicit user choice.random-regen-instead-of-tradeoff-alternatives — when user dislikes a direction, re-rolling without producing 2-3 documented alternatives via templates/alternatives/tradeoff.md.tpl.prefers-reduced-motion test: animations either disabled or shortened to ≤10mstransform, opacity, filter, color, background-color, box-shadow — never width/height/top/left/margin/paddingwidth/height/top/left (causes reflow; use transform)prefers-reduced-motion fallback (a11y violation)agents/_design/creative-director — sets the motion language and WOW-moment strategyagents/_design/prototype-builder — implements animations in HTML/CSS/JSagents/_design/ui-polish-reviewer — reviews motion quality and polishagents/_design/accessibility-reviewer — verifies reduced-motion + a11y complianceagents/_design/ux-ui-designer — owns interaction-design layersupervibe:brandbook — motion.md is source of truth for tiers + easingssupervibe:prototype — invokes this skill during prototype buildsupervibe:landing-page — invokes this skill for hero animationssupervibe:preview-server — auto-spawned after implementation for live preview