design-motion — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited design-motion (Agent Skill) and scored it 45/100 (orange). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 1 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 1 flagged
A base64 string of 128+ characters appears in a documentation file. Encoded prompt injection hides the hostile instruction in base64 — invisible to keyword filters — and relies on the agent's ability to decode it at runtime. There is no normal authoring reason to embed a multi-hundred-byte base64 blob in skill docs.
*.sig, SIGNATURES) outside the documentation.Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Create purposeful, performant animations that enhance UX without overwhelming users.
Before adding ANY animation, verify:
cat package.json | grep -i "framer-motion\|gsap\|animat"
rg "motion\.|animate\(|useSpring" --type tsx -l | head -10rg "@keyframes|animation:" --type css
cat tailwind.config.* | grep -A20 "animation\|keyframes"rg "prefers-reduced-motion" --type tsx --type cssWhy: Maintain consistent animation language. Respect user motion preferences.
Every animation should serve a purpose:
transform and opacity only (GPU-accelerated)width, height, top, left (trigger layout)will-change sparingly and remove after animationimport { motion } from 'framer-motion'
// Entrance animation
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3, ease: "easeOut" }}
>
Content
</motion.div>
// Exit animation
<motion.div
exit={{ opacity: 0, scale: 0.95 }}
transition={{ duration: 0.2 }}
>
Content
</motion.div><motion.button
whileHover={{ scale: 1.02, boxShadow: "0 4px 20px rgba(0,0,0,0.1)" }}
whileTap={{ scale: 0.98 }}
transition={{ type: "spring", stiffness: 400, damping: 17 }}
>
Click me
</motion.button>const container = {
hidden: { opacity: 0 },
show: {
opacity: 1,
transition: { staggerChildren: 0.05 }
}
}
const item = {
hidden: { opacity: 0, x: -20 },
show: { opacity: 1, x: 0 }
}
<motion.ul variants={container} initial="hidden" animate="show">
{items.map(i => (
<motion.li key={i.id} variants={item}>{i.name}</motion.li>
))}
</motion.ul>import { motion, useScroll, useTransform } from 'framer-motion'
function ParallaxSection() {
const { scrollYProgress } = useScroll()
const y = useTransform(scrollYProgress, [0, 1], [0, -100])
const opacity = useTransform(scrollYProgress, [0, 0.5, 1], [1, 1, 0])
return (
<motion.div style={{ y, opacity }}>
Parallax content
</motion.div>
)
}// app/template.tsx
'use client'
import { motion } from 'framer-motion'
export default function Template({ children }: { children: React.ReactNode }) {
return (
<motion.div
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -10 }}
transition={{ duration: 0.3 }}
>
{children}
</motion.div>
)
}// Shared layout animations
<motion.div layout layoutId="card">
{isExpanded ? <ExpandedCard /> : <CollapsedCard />}
</motion.div>
// Smooth height changes
<motion.div
layout
transition={{ layout: { duration: 0.3 } }}
>
{showMore && <AdditionalContent />}
</motion.div>// Fade in
<div className="animate-in fade-in duration-300">
// Slide up
<div className="animate-in slide-in-from-bottom-4 duration-500">
// Combined
<div className="animate-in fade-in slide-in-from-bottom-4 duration-500 delay-150">
// Custom in tailwind.config.ts
animation: {
'float': 'float 3s ease-in-out infinite',
'pulse-slow': 'pulse 3s ease-in-out infinite',
'shimmer': 'shimmer 2s linear infinite',
}
keyframes: {
float: {
'0%, 100%': { transform: 'translateY(0)' },
'50%': { transform: 'translateY(-10px)' },
},
shimmer: {
'0%': { backgroundPosition: '-200% 0' },
'100%': { backgroundPosition: '200% 0' },
},
}<div className="animate-pulse space-y-4">
<div className="h-4 bg-muted rounded w-3/4" />
<div className="h-4 bg-muted rounded w-1/2" />
</div>
// Shimmer effect
<div className="relative overflow-hidden bg-muted rounded">
<div className="absolute inset-0 -translate-x-full animate-shimmer bg-gradient-to-r from-transparent via-white/20 to-transparent" />
</div>import { gsap } from 'gsap'
import { ScrollTrigger } from 'gsap/ScrollTrigger'
import { useGSAP } from '@gsap/react'
gsap.registerPlugin(ScrollTrigger)
function HeroSection() {
const containerRef = useRef(null)
useGSAP(() => {
const tl = gsap.timeline({
scrollTrigger: {
trigger: containerRef.current,
start: "top center",
end: "bottom center",
scrub: 1,
}
})
tl.from(".hero-title", { opacity: 0, y: 100, duration: 1 })
.from(".hero-subtitle", { opacity: 0, y: 50 }, "-=0.5")
.from(".hero-cta", { opacity: 0, scale: 0.8 }, "-=0.3")
}, { scope: containerRef })
return <div ref={containerRef}>...</div>
}// Always respect reduced motion preference
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches
// Framer Motion
<motion.div
initial={prefersReducedMotion ? false : { opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: prefersReducedMotion ? 0 : 0.3 }}
>
// CSS
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
}
}
// Tailwind
<div className="motion-safe:animate-bounce motion-reduce:animate-none">For playful interactions (bouncy buttons, magnetic elements, confetti, Konami code, satisfying toggles, progress milestones), see references/delight-interactions.md.
After implementing animations:
prefers-reduced-motion: reduce~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.